I2P -> .NET

This commit is contained in:
Михаил Подивилов 2019-05-11 18:25:50 +03:00
parent f176f1909b
commit fdb0ce6703
272 changed files with 5702 additions and 8931 deletions

305
libdotnet/Base.cpp Normal file
View file

@ -0,0 +1,305 @@
#include <stdlib.h>
#include <string.h>
#include "Base.h"
namespace dotnet
{
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;
}
std::string ToBase64Standard (const std::string& in)
{
auto len = Base64EncodingBufferSize (in.length ());
char * str = new char[len+1];
auto l = ByteStreamToBase64 ((const uint8_t *)in.c_str (), in.length (), str, len);
str[l] = 0;
// replace '-' by '+' and '~' by '/'
for (size_t i = 0; i < l; i++)
if (str[i] == '-') str[i] = '+';
else if (str[i] == '~') str[i] = '/';
std::string s(str);
delete[] str;
return s;
}
/*
*
* 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;
}
}
}

28
libdotnet/Base.h Normal file
View file

@ -0,0 +1,28 @@
#ifndef BASE_H__
#define BASE_H__
#include <inttypes.h>
#include <string>
#include <iostream>
namespace dotnet {
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);
std::string ToBase64Standard (const std::string& in); // using standard table, for Proxy-Authorization
} // data
} // dotnet
#endif

69
libdotnet/BloomFilter.cpp Normal file
View file

@ -0,0 +1,69 @@
#include "BloomFilter.h"
#include "DotNetEndian.h"
#include <array>
#include <openssl/sha.h>
namespace dotnet
{
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
libdotnet/BloomFilter.h Normal file
View file

@ -0,0 +1,31 @@
#ifndef BLOOM_FILTER_H_
#define BLOOM_FILTER_H_
#include <memory>
#include <cstdint>
namespace dotnet
{
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

55
libdotnet/CPU.cpp Normal file
View file

@ -0,0 +1,55 @@
#include "CPU.h"
#if defined(__x86_64__) || defined(__i386__)
#include <cpuid.h>
#endif
#include "Log.h"
#ifndef bit_AES
#define bit_AES (1 << 25)
#endif
#ifndef bit_AVX
#define bit_AVX (1 << 28)
#endif
namespace dotnet
{
namespace cpu
{
bool aesni = false;
bool avx = false;
void Detect()
{
#if defined(__AES__) || defined(__AVX__)
#if defined(__x86_64__) || defined(__i386__)
int info[4];
__cpuid(0, info[0], info[1], info[2], info[3]);
if (info[0] >= 0x00000001) {
__cpuid(0x00000001, info[0], info[1], info[2], info[3]);
#ifdef __AES__
aesni = info[2] & bit_AES; // AESNI
#endif // __AES__
#ifdef __AVX__
avx = info[2] & bit_AVX; // AVX
#endif // __AVX__
}
#endif // defined(__x86_64__) || defined(__i386__)
#ifdef __AES__
if(aesni)
{
LogPrint(eLogInfo, "AESNI enabled");
}
#endif // __AES__
#ifdef __AVX__
if(avx)
{
LogPrint(eLogInfo, "AVX enabled");
}
#endif // __AVX__
#endif // defined(__AES__) || defined(__AVX__)
}
}
}

15
libdotnet/CPU.h Normal file
View file

@ -0,0 +1,15 @@
#ifndef LIBDOTNET_CPU_H
#define LIBDOTNET_CPU_H
namespace dotnet
{
namespace cpu
{
extern bool aesni;
extern bool avx;
void Detect();
}
}
#endif

138
libdotnet/ChaCha20.cpp Normal file
View file

@ -0,0 +1,138 @@
/*
* Copyright (c) 2013-2018, The PurpleI2P Project
*
* This file is part of Purple dotnet project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*
* Kovri go write your own code
*
*/
#include "DotNetEndian.h"
#include "ChaCha20.h"
#if !OPENSSL_AEAD_CHACHA20_POLY1305
namespace dotnet
{
namespace crypto
{
namespace chacha
{
void u32t8le(uint32_t v, uint8_t * p)
{
p[0] = v & 0xff;
p[1] = (v >> 8) & 0xff;
p[2] = (v >> 16) & 0xff;
p[3] = (v >> 24) & 0xff;
}
uint32_t u8t32le(const uint8_t * p)
{
uint32_t value = p[3];
value = (value << 8) | p[2];
value = (value << 8) | p[1];
value = (value << 8) | p[0];
return value;
}
uint32_t rotl32(uint32_t x, int n)
{
return x << n | (x >> (-n & 31));
}
void quarterround(uint32_t *x, int a, int b, int c, int d)
{
x[a] += x[b]; x[d] = rotl32(x[d] ^ x[a], 16);
x[c] += x[d]; x[b] = rotl32(x[b] ^ x[c], 12);
x[a] += x[b]; x[d] = rotl32(x[d] ^ x[a], 8);
x[c] += x[d]; x[b] = rotl32(x[b] ^ x[c], 7);
}
void Chacha20Block::operator << (const Chacha20State & st)
{
int i;
for (i = 0; i < 16; i++)
u32t8le(st.data[i], data + (i << 2));
}
void block (Chacha20State &input, int rounds)
{
int i;
Chacha20State x;
x.Copy(input);
for (i = rounds; i > 0; i -= 2)
{
quarterround(x.data, 0, 4, 8, 12);
quarterround(x.data, 1, 5, 9, 13);
quarterround(x.data, 2, 6, 10, 14);
quarterround(x.data, 3, 7, 11, 15);
quarterround(x.data, 0, 5, 10, 15);
quarterround(x.data, 1, 6, 11, 12);
quarterround(x.data, 2, 7, 8, 13);
quarterround(x.data, 3, 4, 9, 14);
}
x += input;
input.block << x;
}
void Chacha20Init (Chacha20State& state, const uint8_t * nonce, const uint8_t * key, uint32_t counter)
{
state.data[0] = 0x61707865;
state.data[1] = 0x3320646e;
state.data[2] = 0x79622d32;
state.data[3] = 0x6b206574;
for (size_t i = 0; i < 8; i++)
state.data[4 + i] = chacha::u8t32le(key + i * 4);
state.data[12] = htole32 (counter);
for (size_t i = 0; i < 3; i++)
state.data[13 + i] = chacha::u8t32le(nonce + i * 4);
}
void Chacha20SetCounter (Chacha20State& state, uint32_t counter)
{
state.data[12] = htole32 (counter);
state.offset = 0;
}
void Chacha20Encrypt (Chacha20State& state, uint8_t * buf, size_t sz)
{
if (state.offset > 0)
{
// previous block if any
auto s = chacha::blocksize - state.offset;
if (sz < s) s = sz;
for (size_t i = 0; i < s; i++)
buf[i] ^= state.block.data[state.offset + i];
buf += s;
sz -= s;
state.offset += s;
if (state.offset >= chacha::blocksize) state.offset = 0;
}
for (size_t i = 0; i < sz; i += chacha::blocksize)
{
chacha::block(state, chacha::rounds);
state.data[12]++;
for (size_t j = i; j < i + chacha::blocksize; j++)
{
if (j >= sz)
{
state.offset = j & 0x3F; // % 64
break;
}
buf[j] ^= state.block.data[j - i];
}
}
}
} // namespace chacha
}
}
#endif

72
libdotnet/ChaCha20.h Normal file
View file

@ -0,0 +1,72 @@
/*
* Copyright (c) 2013-2018, The PurpleI2P Project
*
* This file is part of Purple dotnet project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*
* Kovri go write your own code
*
*/
#ifndef LIBDOTNET_CHACHA20_H
#define LIBDOTNET_CHACHA20_H
#include <cstdint>
#include <cstring>
#include <inttypes.h>
#include <string.h>
#include "Crypto.h"
#if !OPENSSL_AEAD_CHACHA20_POLY1305
namespace dotnet
{
namespace crypto
{
const std::size_t CHACHA20_KEY_BYTES = 32;
const std::size_t CHACHA20_NOUNCE_BYTES = 12;
namespace chacha
{
constexpr std::size_t blocksize = 64;
constexpr int rounds = 20;
struct Chacha20State;
struct Chacha20Block
{
Chacha20Block () {};
Chacha20Block (Chacha20Block &&) = delete;
uint8_t data[blocksize];
void operator << (const Chacha20State & st);
};
struct Chacha20State
{
Chacha20State (): offset (0) {};
Chacha20State (Chacha20State &&) = delete;
Chacha20State & operator += (const Chacha20State & other)
{
for(int i = 0; i < 16; i++)
data[i] += other.data[i];
return *this;
}
void Copy(const Chacha20State & other)
{
memcpy(data, other.data, sizeof(uint32_t) * 16);
}
uint32_t data[16];
Chacha20Block block;
size_t offset;
};
void Chacha20Init (Chacha20State& state, const uint8_t * nonce, const uint8_t * key, uint32_t counter);
void Chacha20SetCounter (Chacha20State& state, uint32_t counter);
void Chacha20Encrypt (Chacha20State& state, uint8_t * buf, size_t sz); // encrypt buf in place
}
}
}
#endif
#endif

369
libdotnet/Config.cpp Normal file
View file

@ -0,0 +1,369 @@
/*
* Copyright (c) 2013-2017, The PurpleI2P Project
*
* This file is part of Purple dotnet 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 dotnet {
namespace config {
options_description m_OptionsDesc;
variables_map m_Options;
void Init()
{
options_description general("General options");
general.add_options()
("help", "Show this message")
("version", "Show dotnet version")
("conf", value<std::string>()->default_value(""), "Path to main dotnet config file (default: try ~/.dotnet/dotnet.conf or /var/lib/dotnet/dotnet.conf)")
("tunconf", value<std::string>()->default_value(""), "Path to config with tunnels list and options (default: try ~/.dotnet/tunnels.conf or /var/lib/dotnet/tunnels.conf)")
("tunnelsdir", value<std::string>()->default_value(""), "Path to extra tunnels' configs folder (default: ~/.dotnet/tunnels.d or /var/lib/dotnet/tunnels.d")
("pidfile", value<std::string>()->default_value(""), "Path to pidfile (default: ~/dotnet/dotnet.pid or /var/lib/dotnet/dotnet.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, none)")
("logclftime", bool_switch()->default_value(false), "Write full CLF-formatted date and time to log (default: disabled, write only time)")
("family", value<std::string>()->default_value(""), "Specify a family, router belongs to")
("datadir", value<std::string>()->default_value(""), "Path to storage of dotnet 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>()->default_value(true), "Should we assume we are behind NAT? (default: enabled)")
("port", value<uint16_t>()->default_value(0), "Port to listen for incoming connections (default: auto)")
("ipv4", value<bool>()->default_value(true), "Enable communication through ipv4 (default: enabled)")
("ipv6", bool_switch()->default_value(false), "Enable communication through ipv6 (default: disabled)")
("netid", value<int>()->default_value(DOTNET_NET_ID), "Specify NetID")
("daemon", bool_switch()->default_value(false), "Router will go to background after start (default: disabled)")
("service", bool_switch()->default_value(false), "Router will use system folders like '/var/lib/dotnet' (default: disabled)")
("notransit", bool_switch()->default_value(false), "Router will not accept transit tunnels at startup (default: disabled)")
("floodfill", bool_switch()->default_value(false), "Router will be floodfill (default: disabled)")
("bandwidth", value<std::string>()->default_value(""), "Bandwidth limit: integer in KBps or letters: L (32), O (256), P (2048), X (>9000)")
("share", value<int>()->default_value(100), "Limit of transit traffic from max bandwidth in percents. (default: 100)")
("ntcp", value<bool>()->default_value(true), "Enable NTCP transport (default: enabled)")
("ssu", value<bool>()->default_value(true), "Enable SSU transport (default: enabled)")
("ntcpproxy", value<std::string>()->default_value(""), "Proxy URL for NTCP transport")
#ifdef _WIN32
("svcctl", value<std::string>()->default_value(""), "Windows service management ('install' or 'remove')")
("insomnia", bool_switch()->default_value(false), "Prevent system from sleeping (default: disabled)")
("close", value<std::string>()->default_value("ask"), "Action on close: minimize, exit, ask")
#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)")
("limits.ntcpsoft", value<uint16_t>()->default_value(0), "Threshold to start probabilistic backoff with ntcp sessions (default: use system limit)")
("limits.ntcphard", value<uint16_t>()->default_value(0), "Maximum number of ntcp sessions (default: use system limit)")
("limits.ntcpthreads", value<uint16_t>()->default_value(1), "Maximum number of threads used by NTCP DH worker (default: 1)")
;
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("dotnet"), "Username for basic auth")
("http.pass", value<std::string>()->default_value(""), "Password for basic auth (default: random, see logs)")
("http.strictheaders", value<bool>()->default_value(true), "Enable strict host checking on WebUI")
("http.hostname", value<std::string>()->default_value("localhost"), "Expected hostname for WebUI")
("http.webroot", value<std::string>()->default_value("/"), "WebUI root path (default: / )")
;
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<dotnet::data::SigningKeyType>()->default_value(dotnet::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<dotnet::data::SigningKeyType>()->default_value(dotnet::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.enabled", value<bool>()->default_value(false), "Enable or disable SOCKS outproxy")
("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(true), "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 dncp("DNCP options");
dncp.add_options()
("dncp.enabled", value<bool>()->default_value(false), "Enable or disable DNCP")
("dncp.address", value<std::string>()->default_value("127.0.0.1"), "DNCP listen address")
("dncp.port", value<uint16_t>()->default_value(7654), "DNCP listen port")
;
options_description dotnetcontrol("DotNetControl options");
dotnetcontrol.add_options()
("dotnetcontrol.enabled", value<bool>()->default_value(false), "Enable or disable DOTNET Control Protocol")
("dotnetcontrol.address", value<std::string>()->default_value("127.0.0.1"), "DOTNETCP listen address")
("dotnetcontrol.port", value<uint16_t>()->default_value(7650), "DOTNETCP listen port")
("dotnetcontrol.password", value<std::string>()->default_value("dotnet"), "DOTNETCP access password")
("dotnetcontrol.cert", value<std::string>()->default_value("dotnetcontrol.crt.pem"), "DOTNETCP connection certificate")
("dotnetcontrol.key", value<std::string>()->default_value("dotnetcontrol.key.pem"), "DOTNETCP connection certificate 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(".NET"), "Name dotnet appears in UPnP forwarding 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.proxy", value<std::string>()->default_value(""), "url for reseed proxy, supports http/socks")
("reseed.urls", value<std::string>()->default_value(
"https://reseed.podivilov.ru/,",
"https://reseed.podivilov.com/"
), "Reseed URLs, separated by comma")
;
options_description addressbook("AddressBook options");
addressbook.add_options()
("addressbook.defaulturl", value<std::string>()->default_value(
"http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.dotnet/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")
;
options_description ntcp2("NTCP2 Options");
ntcp2.add_options()
("ntcp2.enabled", value<bool>()->default_value(true), "Enable NTCP2 (default: enabled)")
("ntcp2.published", value<bool>()->default_value(false), "Publish NTCP2 (default: disabled)")
("ntcp2.port", value<uint16_t>()->default_value(0), "Port to listen for incoming NTCP2 connections (default: auto)")
("ntcp2.addressv6", value<std::string>()->default_value("::"), "Address to bind NTCP2 on")
;
options_description nettime("Time sync options");
nettime.add_options()
("nettime.enabled", value<bool>()->default_value(false), "Disable time sync (default: disabled)")
("nettime.ntpservers", value<std::string>()->default_value(
"0.pool.ntp.org,"
"1.pool.ntp.org,"
"2.pool.ntp.org,"
"3.pool.ntp.org"
), "Comma separated list of NTCP servers")
("nettime.ntpsyncinterval", value<int>()->default_value(72), "NTP sync interval in hours (default: 72)")
;
options_description persist("Network information persisting options");
persist.add_options()
("persist.profiles", value<bool>()->default_value(true), "Persist peer profiles (default: true)")
("persist.addressbook", value<bool>()->default_value(true), "Persist full addresses (default: true)")
;
m_OptionsDesc
.add(general)
.add(limits)
.add(httpserver)
.add(httpproxy)
.add(socksproxy)
.add(sam)
.add(bob)
.add(dncp)
.add(dotnetcontrol)
.add(upnp)
.add(precomputation)
.add(reseed)
.add(addressbook)
.add(trust)
.add(websocket)
.add(exploratory)
.add(ntcp2)
.add(nettime)
.add(persist)
;
}
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 << "dotnet version " << DOTNET_VERSION << " (" << DOTNET_VERSION << ")" << std::endl;
std::cout << m_OptionsDesc;
exit(EXIT_SUCCESS);
}
else if (m_Options.count("version"))
{
std::cout << "dotnet version " << DOTNET_VERSION << " (" << DOTNET_VERSION << ")" << std::endl;
std::cout << "Boost version "
<< BOOST_VERSION / 100000 << "." // maj. version
<< BOOST_VERSION / 100 % 1000 << "." // min. version
<< BOOST_VERSION % 100 // patch version
<< std::endl;
#if defined(OPENSSL_VERSION_TEXT)
std::cout << OPENSSL_VERSION_TEXT << std::endl;
#endif
#if defined(LIBRESSL_VERSION_TEXT)
std::cout << LIBRESSL_VERSION_TEXT << std::endl;
#endif
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;
}
bool GetOptionAsAny(const char *name, boost::any& value)
{
if (!m_Options.count(name))
return false;
value = m_Options[name];
return true;
}
bool GetOptionAsAny(const std::string& name, boost::any& value)
{
return GetOptionAsAny (name.c_str (), value);
}
} // namespace config
} // namespace dotnet

116
libdotnet/Config.h Normal file
View file

@ -0,0 +1,116 @@
#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 dotnet 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 dotnet {
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 its list with description
* and 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 the 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 occurred 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);
}
bool GetOptionAsAny(const char *name, boost::any& value);
bool GetOptionAsAny(const std::string& name, boost::any& 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 otherwise
*/
bool IsDefault(const char *name);
}
}
#endif // CONFIG_H

1332
libdotnet/Crypto.cpp Normal file

File diff suppressed because it is too large Load diff

382
libdotnet/Crypto.h Normal file
View file

@ -0,0 +1,382 @@
#ifndef CRYPTO_H__
#define CRYPTO_H__
#include <inttypes.h>
#include <string>
#include <vector>
#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 <openssl/opensslv.h>
#include "Base.h"
#include "Tag.h"
#include "CPU.h"
// recognize openssl version and features
#if ((OPENSSL_VERSION_NUMBER < 0x010100000) || defined(LIBRESSL_VERSION_NUMBER)) // 1.0.2 and below or LibreSSL
# define LEGACY_OPENSSL 1
# define X509_getm_notBefore X509_get_notBefore
# define X509_getm_notAfter X509_get_notAfter
#else
# define LEGACY_OPENSSL 0
# define OPENSSL_HKDF 1
# if (OPENSSL_VERSION_NUMBER >= 0x010101000) // 1.1.1
# define OPENSSL_EDDSA 1
# define OPENSSL_X25519 1
# define OPENSSL_SIPHASH 1
# endif
# if !defined OPENSSL_NO_CHACHA && !defined OPENSSL_NO_POLY1305 // some builds might not include them
# define OPENSSL_AEAD_CHACHA20_POLY1305 1
# endif
#endif
namespace dotnet
{
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];
};
// x25519
class X25519Keys
{
public:
X25519Keys ();
X25519Keys (const uint8_t * priv, const uint8_t * pub); // for RouterContext
~X25519Keys ();
void GenerateKeys ();
const uint8_t * GetPublicKey () const { return m_PublicKey; };
void GetPrivateKey (uint8_t * priv) const;
void Agree (const uint8_t * pub, uint8_t * shared);
private:
uint8_t m_PublicKey[32];
#if OPENSSL_X25519
EVP_PKEY_CTX * m_Ctx;
EVP_PKEY * m_Pkey;
#else
BN_CTX * m_Ctx;
uint8_t m_PrivateKey[32];
#endif
};
// 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);
// ECIES
void ECIESEncrypt (const EC_GROUP * curve, const EC_POINT * key, const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding = false); // 222 bytes data, 514 bytes encrypted with zeropadding, 512 without
bool ECIESDecrypt (const EC_GROUP * curve, const BIGNUM * key, const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding = false);
void GenerateECIESKeyPair (const EC_GROUP * curve, BIGNUM *& priv, EC_POINT *& pub);
// HMAC
typedef dotnet::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 (!(((size_t)buf | (size_t)other.buf) & 0x03)) // multiple of 4 ?
{
for (int i = 0; i < 4; i++)
reinterpret_cast<uint32_t *>(buf)[i] ^= reinterpret_cast<const uint32_t *>(other.buf)[i];
}
else
{
for (int i = 0; i < 16; i++)
buf[i] ^= other.buf[i];
}
}
};
typedef dotnet::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 __AES__
#ifdef ARM64AES
void init_aesenc(void) __attribute__((constructor));
#endif
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
};
#endif
#ifdef __AES__
class ECBEncryption: public ECBCryptoAESNI
#else
class ECBEncryption
#endif
{
public:
void SetKey (const AESKey& key);
void Encrypt(const ChipherBlock * in, ChipherBlock * out);
private:
AES_KEY m_Key;
};
#ifdef __AES__
class ECBDecryption: public ECBCryptoAESNI
#else
class ECBDecryption
#endif
{
public:
void SetKey (const AESKey& key);
void Decrypt (const ChipherBlock * in, ChipherBlock * out);
private:
AES_KEY m_Key;
};
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 GetIV (uint8_t * iv) const { memcpy (iv, (const uint8_t *)m_LastBlock, 16); };
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
ECBEncryption & ECB() { return m_ECBEncryption; }
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 GetIV (uint8_t * iv) const { memcpy (iv, (const uint8_t *)m_IV, 16); };
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
ECBDecryption & ECB() { return m_ECBDecryption; }
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;
CBCEncryption m_LayerEncryption;
};
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;
CBCDecryption m_LayerDecryption;
};
// AEAD/ChaCha20/Poly1305
bool AEADChaCha20Poly1305 (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len, bool encrypt); // msgLen is len without tag
void AEADChaCha20Poly1305Encrypt (const std::vector<std::pair<uint8_t *, size_t> >& bufs, const uint8_t * key, const uint8_t * nonce, uint8_t * mac); // encrypt multiple buffers with zero ad
// ChaCha20
void ChaCha20 (const uint8_t * msg, size_t msgLen, const uint8_t * key, const uint8_t * nonce, uint8_t * out);
// HKDF
void HKDF (const uint8_t * salt, const uint8_t * key, size_t keyLen, const std::string& info, uint8_t * out); // salt - 32, out - 64, info <= 32
// init and terminate
void InitCrypto (bool precomputation);
void TerminateCrypto ();
}
}
// take care about openssl below 1.1.0
#if LEGACY_OPENSSL
// 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

151
libdotnet/CryptoKey.cpp Normal file
View file

@ -0,0 +1,151 @@
#include <string.h>
#include "Log.h"
#include "Gost.h"
#include "CryptoKey.h"
namespace dotnet
{
namespace crypto
{
ElGamalEncryptor::ElGamalEncryptor (const uint8_t * pub)
{
memcpy (m_PublicKey, pub, 256);
}
void ElGamalEncryptor::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding)
{
ElGamalEncrypt (m_PublicKey, data, encrypted, ctx, zeroPadding);
}
ElGamalDecryptor::ElGamalDecryptor (const uint8_t * priv)
{
memcpy (m_PrivateKey, priv, 256);
}
bool ElGamalDecryptor::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding)
{
return ElGamalDecrypt (m_PrivateKey, encrypted, data, ctx, zeroPadding);
}
ECIESP256Encryptor::ECIESP256Encryptor (const uint8_t * pub)
{
m_Curve = EC_GROUP_new_by_curve_name (NID_X9_62_prime256v1);
m_PublicKey = EC_POINT_new (m_Curve);
BIGNUM * x = BN_bin2bn (pub, 32, nullptr);
BIGNUM * y = BN_bin2bn (pub + 32, 32, nullptr);
if (!EC_POINT_set_affine_coordinates_GFp (m_Curve, m_PublicKey, x, y, nullptr))
LogPrint (eLogError, "ECICS P256 invalid public key");
BN_free (x); BN_free (y);
}
ECIESP256Encryptor::~ECIESP256Encryptor ()
{
if (m_Curve) EC_GROUP_free (m_Curve);
if (m_PublicKey) EC_POINT_free (m_PublicKey);
}
void ECIESP256Encryptor::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding)
{
if (m_Curve && m_PublicKey)
ECIESEncrypt (m_Curve, m_PublicKey, data, encrypted, ctx, zeroPadding);
}
ECIESP256Decryptor::ECIESP256Decryptor (const uint8_t * priv)
{
m_Curve = EC_GROUP_new_by_curve_name (NID_X9_62_prime256v1);
m_PrivateKey = BN_bin2bn (priv, 32, nullptr);
}
ECIESP256Decryptor::~ECIESP256Decryptor ()
{
if (m_Curve) EC_GROUP_free (m_Curve);
if (m_PrivateKey) BN_free (m_PrivateKey);
}
bool ECIESP256Decryptor::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding)
{
if (m_Curve && m_PrivateKey)
return ECIESDecrypt (m_Curve, m_PrivateKey, encrypted, data, ctx, zeroPadding);
return false;
}
void CreateECIESP256RandomKeys (uint8_t * priv, uint8_t * pub)
{
EC_GROUP * curve = EC_GROUP_new_by_curve_name (NID_X9_62_prime256v1);
EC_POINT * p = nullptr;
BIGNUM * key = nullptr;
GenerateECIESKeyPair (curve, key, p);
bn2buf (key, priv, 32);
RAND_bytes (priv + 32, 224);
BN_free (key);
BIGNUM * x = BN_new (), * y = BN_new ();
EC_POINT_get_affine_coordinates_GFp (curve, p, x, y, NULL);
bn2buf (x, pub, 32);
bn2buf (y, pub + 32, 32);
RAND_bytes (pub + 64, 192);
EC_POINT_free (p);
BN_free (x); BN_free (y);
EC_GROUP_free (curve);
}
ECIESGOSTR3410Encryptor::ECIESGOSTR3410Encryptor (const uint8_t * pub)
{
auto& curve = GetGOSTR3410Curve (eGOSTR3410CryptoProA);
m_PublicKey = EC_POINT_new (curve->GetGroup ());
BIGNUM * x = BN_bin2bn (pub, 32, nullptr);
BIGNUM * y = BN_bin2bn (pub + 32, 32, nullptr);
if (!EC_POINT_set_affine_coordinates_GFp (curve->GetGroup (), m_PublicKey, x, y, nullptr))
LogPrint (eLogError, "ECICS GOST R 34.10 invalid public key");
BN_free (x); BN_free (y);
}
ECIESGOSTR3410Encryptor::~ECIESGOSTR3410Encryptor ()
{
if (m_PublicKey) EC_POINT_free (m_PublicKey);
}
void ECIESGOSTR3410Encryptor::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding)
{
if (m_PublicKey)
ECIESEncrypt (GetGOSTR3410Curve (eGOSTR3410CryptoProA)->GetGroup (), m_PublicKey, data, encrypted, ctx, zeroPadding);
}
ECIESGOSTR3410Decryptor::ECIESGOSTR3410Decryptor (const uint8_t * priv)
{
m_PrivateKey = BN_bin2bn (priv, 32, nullptr);
}
ECIESGOSTR3410Decryptor::~ECIESGOSTR3410Decryptor ()
{
if (m_PrivateKey) BN_free (m_PrivateKey);
}
bool ECIESGOSTR3410Decryptor::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding)
{
if (m_PrivateKey)
return ECIESDecrypt (GetGOSTR3410Curve (eGOSTR3410CryptoProA)->GetGroup (), m_PrivateKey, encrypted, data, ctx, zeroPadding);
return false;
}
void CreateECIESGOSTR3410RandomKeys (uint8_t * priv, uint8_t * pub)
{
auto& curve = GetGOSTR3410Curve (eGOSTR3410CryptoProA);
EC_POINT * p = nullptr;
BIGNUM * key = nullptr;
GenerateECIESKeyPair (curve->GetGroup (), key, p);
bn2buf (key, priv, 32);
RAND_bytes (priv + 32, 224);
BN_free (key);
BIGNUM * x = BN_new (), * y = BN_new ();
EC_POINT_get_affine_coordinates_GFp (curve->GetGroup (), p, x, y, NULL);
bn2buf (x, pub, 32);
bn2buf (y, pub + 32, 32);
RAND_bytes (pub + 64, 192);
EC_POINT_free (p);
BN_free (x); BN_free (y);
}
}
}

123
libdotnet/CryptoKey.h Normal file
View file

@ -0,0 +1,123 @@
#ifndef CRYPTO_KEY_H__
#define CRYPTO_KEY_H__
#include <inttypes.h>
#include "Crypto.h"
namespace dotnet
{
namespace crypto
{
class CryptoKeyEncryptor
{
public:
virtual ~CryptoKeyEncryptor () {};
virtual void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding) = 0; // 222 bytes data, 512/514 bytes encrypted
};
class CryptoKeyDecryptor
{
public:
virtual ~CryptoKeyDecryptor () {};
virtual bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding) = 0; // 512/514 bytes encrypted, 222 bytes data
virtual size_t GetPublicKeyLen () const = 0; // we need it to set key in LS2
};
// ElGamal
class ElGamalEncryptor: public CryptoKeyEncryptor // for destination
{
public:
ElGamalEncryptor (const uint8_t * pub);
void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding);
private:
uint8_t m_PublicKey[256];
};
class ElGamalDecryptor: public CryptoKeyDecryptor // for destination
{
public:
ElGamalDecryptor (const uint8_t * priv);
bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding);
size_t GetPublicKeyLen () const { return 256; };
private:
uint8_t m_PrivateKey[256];
};
// ECIES P256
class ECIESP256Encryptor: public CryptoKeyEncryptor
{
public:
ECIESP256Encryptor (const uint8_t * pub);
~ECIESP256Encryptor ();
void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding);
private:
EC_GROUP * m_Curve;
EC_POINT * m_PublicKey;
};
class ECIESP256Decryptor: public CryptoKeyDecryptor
{
public:
ECIESP256Decryptor (const uint8_t * priv);
~ECIESP256Decryptor ();
bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding);
size_t GetPublicKeyLen () const { return 64; };
private:
EC_GROUP * m_Curve;
BIGNUM * m_PrivateKey;
};
void CreateECIESP256RandomKeys (uint8_t * priv, uint8_t * pub);
// ECIES GOST R 34.10
class ECIESGOSTR3410Encryptor: public CryptoKeyEncryptor
{
public:
ECIESGOSTR3410Encryptor (const uint8_t * pub);
~ECIESGOSTR3410Encryptor ();
void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx, bool zeroPadding);
private:
EC_POINT * m_PublicKey;
};
class ECIESGOSTR3410Decryptor: public CryptoKeyDecryptor
{
public:
ECIESGOSTR3410Decryptor (const uint8_t * priv);
~ECIESGOSTR3410Decryptor ();
bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx, bool zeroPadding);
size_t GetPublicKeyLen () const { return 64; };
private:
BIGNUM * m_PrivateKey;
};
void CreateECIESGOSTR3410RandomKeys (uint8_t * priv, uint8_t * pub);
}
}
#endif

81
libdotnet/CryptoWorker.h Normal file
View file

@ -0,0 +1,81 @@
#ifndef CRYPTO_WORKER_H_
#define CRYPTO_WORKER_H_
#include <condition_variable>
#include <mutex>
#include <deque>
#include <thread>
#include <vector>
#include <memory>
namespace dotnet
{
namespace worker
{
template<typename Caller>
struct ThreadPool
{
typedef std::function<void(void)> ResultFunc;
typedef std::function<ResultFunc(void)> WorkFunc;
typedef std::pair<std::shared_ptr<Caller>, WorkFunc> Job;
typedef std::mutex mtx_t;
typedef std::unique_lock<mtx_t> lock_t;
typedef std::condition_variable cond_t;
ThreadPool(int workers)
{
stop = false;
if(workers > 0)
{
while(workers--)
{
threads.emplace_back([this] {
for (;;)
{
Job job;
{
lock_t lock(this->queue_mutex);
this->condition.wait(
lock, [this] { return this->stop || !this->jobs.empty(); });
if (this->stop && this->jobs.empty()) return;
job = std::move(this->jobs.front());
this->jobs.pop_front();
}
ResultFunc result = job.second();
job.first->GetService().post(result);
}
});
}
}
};
void Offer(const Job & job)
{
{
lock_t lock(queue_mutex);
if (stop) return;
jobs.emplace_back(job);
}
condition.notify_one();
}
~ThreadPool()
{
{
lock_t lock(queue_mutex);
stop = true;
}
condition.notify_all();
for(auto &t: threads) t.join();
}
std::vector<std::thread> threads;
std::deque<Job> jobs;
mtx_t queue_mutex;
cond_t condition;
bool stop;
};
}
}
#endif

692
libdotnet/DNNPProtocol.cpp Normal file
View file

@ -0,0 +1,692 @@
#include <string.h>
#include <atomic>
#include "Base.h"
#include "Log.h"
#include "Crypto.h"
#include "DotNetEndian.h"
#include "Timestamp.h"
#include "RouterContext.h"
#include "NetDb.hpp"
#include "Tunnel.h"
#include "Transports.h"
#include "Garlic.h"
#include "DNNPProtocol.h"
#include "version.h"
using namespace dotnet::transport;
namespace dotnet
{
std::shared_ptr<DNNPMessage> NewDNNPMessage ()
{
return std::make_shared<DNNPMessageBuffer<DNNP_MAX_MESSAGE_SIZE> >();
}
std::shared_ptr<DNNPMessage> NewDNNPShortMessage ()
{
return std::make_shared<DNNPMessageBuffer<DNNP_MAX_SHORT_MESSAGE_SIZE> >();
}
std::shared_ptr<DNNPMessage> NewDNNPTunnelMessage ()
{
auto msg = new DNNPMessageBuffer<dotnet::tunnel::TUNNEL_DATA_MSG_SIZE + DNNP_HEADER_SIZE + 34>(); // reserved for alignment and NTCP 16 + 6 + 12
msg->Align (12);
return std::shared_ptr<DNNPMessage>(msg);
}
std::shared_ptr<DNNPMessage> NewDNNPMessage (size_t len)
{
return (len < DNNP_MAX_SHORT_MESSAGE_SIZE - DNNP_HEADER_SIZE - 2) ? NewDNNPShortMessage () : NewDNNPMessage ();
}
void DNNPMessage::FillDNNPMessageHeader (DNNPMessageType msgType, uint32_t replyMsgID)
{
SetTypeID (msgType);
if (!replyMsgID) RAND_bytes ((uint8_t *)&replyMsgID, 4);
SetMsgID (replyMsgID);
SetExpiration (dotnet::util::GetMillisecondsSinceEpoch () + DNNP_MESSAGE_EXPIRATION_TIMEOUT);
UpdateSize ();
UpdateChks ();
}
void DNNPMessage::RenewDNNPMessageHeader ()
{
uint32_t msgID;
RAND_bytes ((uint8_t *)&msgID, 4);
SetMsgID (msgID);
SetExpiration (dotnet::util::GetMillisecondsSinceEpoch () + DNNP_MESSAGE_EXPIRATION_TIMEOUT);
}
bool DNNPMessage::IsExpired () const
{
auto ts = dotnet::util::GetMillisecondsSinceEpoch ();
auto exp = GetExpiration ();
return (ts > exp + DNNP_MESSAGE_CLOCK_SKEW) || (ts < exp - 3*DNNP_MESSAGE_CLOCK_SKEW); // check if expired or too far in future
}
std::shared_ptr<DNNPMessage> CreateDNNPMessage (DNNPMessageType msgType, const uint8_t * buf, size_t len, uint32_t replyMsgID)
{
auto msg = NewDNNPMessage (len);
if (msg->Concat (buf, len) < len)
LogPrint (eLogError, "DNNP: message length ", len, " exceeds max length ", msg->maxLen);
msg->FillDNNPMessageHeader (msgType, replyMsgID);
return msg;
}
std::shared_ptr<DNNPMessage> CreateDNNPMessage (const uint8_t * buf, size_t len, std::shared_ptr<dotnet::tunnel::InboundTunnel> from)
{
auto msg = NewDNNPMessage ();
if (msg->offset + len < msg->maxLen)
{
memcpy (msg->GetBuffer (), buf, len);
msg->len = msg->offset + len;
msg->from = from;
}
else
LogPrint (eLogError, "DNNP: message length ", len, " exceeds max length");
return msg;
}
std::shared_ptr<DNNPMessage> CopyDNNPMessage (std::shared_ptr<DNNPMessage> msg)
{
if (!msg) return nullptr;
auto newMsg = NewDNNPMessage (msg->len);
newMsg->offset = msg->offset;
*newMsg = *msg;
return newMsg;
}
std::shared_ptr<DNNPMessage> CreateDeliveryStatusMsg (uint32_t msgID)
{
auto m = NewDNNPShortMessage ();
uint8_t * buf = m->GetPayload ();
if (msgID)
{
htobe32buf (buf + DELIVERY_STATUS_MSGID_OFFSET, msgID);
htobe64buf (buf + DELIVERY_STATUS_TIMESTAMP_OFFSET, dotnet::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, dotnet::context.GetNetID ());
}
m->len += DELIVERY_STATUS_SIZE;
m->FillDNNPMessageHeader (eDNNPDeliveryStatus);
return m;
}
std::shared_ptr<DNNPMessage> CreateRouterInfoDatabaseLookupMsg (const uint8_t * key, const uint8_t * from,
uint32_t replyTunnelID, bool exploratory, std::set<dotnet::data::IdentHash> * excludedPeers)
{
auto m = excludedPeers ? NewDNNPMessage () : NewDNNPShortMessage ();
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->FillDNNPMessageHeader (eDNNPDatabaseLookup);
return m;
}
std::shared_ptr<DNNPMessage> CreateLeaseSetDatabaseLookupMsg (const dotnet::data::IdentHash& dest,
const std::set<dotnet::data::IdentHash>& excludedFloodfills,
std::shared_ptr<const dotnet::tunnel::InboundTunnel> replyTunnel, const uint8_t * replyKey, const uint8_t * replyTag)
{
int cnt = excludedFloodfills.size ();
auto m = cnt > 0 ? NewDNNPMessage () : NewDNNPShortMessage ();
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->FillDNNPMessageHeader (eDNNPDatabaseLookup);
return m;
}
std::shared_ptr<DNNPMessage> CreateDatabaseSearchReply (const dotnet::data::IdentHash& ident,
std::vector<dotnet::data::IdentHash> routers)
{
auto m = NewDNNPShortMessage ();
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, dotnet::context.GetRouterInfo ().GetIdentHash (), 32);
len += 32;
m->len += len;
m->FillDNNPMessageHeader (eDNNPDatabaseSearchReply);
return m;
}
std::shared_ptr<DNNPMessage> CreateDatabaseStoreMsg (std::shared_ptr<const dotnet::data::RouterInfo> router, uint32_t replyToken)
{
if (!router) // we send own RouterInfo
router = context.GetSharedRouterInfo ();
auto m = NewDNNPShortMessage ();
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
dotnet::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->FillDNNPMessageHeader (eDNNPDatabaseStore);
return m;
}
std::shared_ptr<DNNPMessage> CreateDatabaseStoreMsg (std::shared_ptr<const dotnet::data::LeaseSet> leaseSet)
{
if (!leaseSet) return nullptr;
auto m = NewDNNPShortMessage ();
uint8_t * payload = m->GetPayload ();
memcpy (payload + DATABASE_STORE_KEY_OFFSET, leaseSet->GetIdentHash (), 32);
payload[DATABASE_STORE_TYPE_OFFSET] = leaseSet->GetStoreType (); // 1 for 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->FillDNNPMessageHeader (eDNNPDatabaseStore);
return m;
}
std::shared_ptr<DNNPMessage> CreateDatabaseStoreMsg (std::shared_ptr<const dotnet::data::LocalLeaseSet> leaseSet, uint32_t replyToken, std::shared_ptr<const dotnet::tunnel::InboundTunnel> replyTunnel)
{
if (!leaseSet) return nullptr;
auto m = NewDNNPShortMessage ();
uint8_t * payload = m->GetPayload ();
memcpy (payload + DATABASE_STORE_KEY_OFFSET, leaseSet->GetStoreHash (), 32);
payload[DATABASE_STORE_TYPE_OFFSET] = leaseSet->GetStoreType (); // LeaseSet or LeaseSet2
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->FillDNNPMessageHeader (eDNNPDatabaseStore);
return m;
}
bool IsRouterInfoMsg (std::shared_ptr<DNNPMessage> msg)
{
if (!msg || msg->GetTypeID () != eDNNPDatabaseStore) 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, "DNNP: 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 *)dotnet::context.GetRouterInfo ().GetIdentHash (), 16))
{
LogPrint (eLogDebug, "DNNP: Build request record ", i, " is ours");
BN_CTX * ctx = BN_CTX_new ();
dotnet::context.DecryptTunnelBuildRecord (record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText, ctx);
BN_CTX_free (ctx);
// replace record to reply
if (dotnet::context.AcceptsTunnels () &&
dotnet::tunnel::tunnels.GetTransitTunnels ().size () <= g_MaxNumTransitTunnels &&
!dotnet::transport::transports.IsBandwidthExceeded () &&
!dotnet::transport::transports.IsTransitBandwidthExceeded ())
{
auto transitTunnel = dotnet::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);
dotnet::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
dotnet::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, "DNNP: 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 = dotnet::tunnel::tunnels.GetPendingInboundTunnel (replyMsgID);
if (tunnel)
{
// endpoint of inbound tunnel
LogPrint (eLogDebug, "DNNP: VariableTunnelBuild reply for tunnel ", tunnel->GetTunnelID ());
if (tunnel->HandleTunnelBuildResponse (buf, len))
{
LogPrint (eLogInfo, "DNNP: Inbound tunnel ", tunnel->GetTunnelID (), " has been created");
tunnel->SetState (dotnet::tunnel::eTunnelStateEstablished);
dotnet::tunnel::tunnels.AddInboundTunnel (tunnel);
}
else
{
LogPrint (eLogInfo, "DNNP: Inbound tunnel ", tunnel->GetTunnelID (), " has been declined");
tunnel->SetState (dotnet::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),
eDNNPVariableTunnelBuildReply, buf, len,
bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET)));
}
else
transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET,
CreateDNNPMessage (eDNNPVariableTunnelBuild, 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),
eDNNPTunnelBuildReply, buf, len,
bufbe32toh (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET)));
}
else
transports.SendMessage (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET,
CreateDNNPMessage (eDNNPTunnelBuild, 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, "DNNP: 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 = dotnet::tunnel::tunnels.GetPendingOutboundTunnel (replyMsgID);
if (tunnel)
{
// reply for outbound tunnel
if (tunnel->HandleTunnelBuildResponse (buf, len))
{
LogPrint (eLogInfo, "DNNP: Outbound tunnel ", tunnel->GetTunnelID (), " has been created");
tunnel->SetState (dotnet::tunnel::eTunnelStateEstablished);
dotnet::tunnel::tunnels.AddOutboundTunnel (tunnel);
}
else
{
LogPrint (eLogInfo, "DNNP: Outbound tunnel ", tunnel->GetTunnelID (), " has been declined");
tunnel->SetState (dotnet::tunnel::eTunnelStateBuildFailed);
}
}
else
LogPrint (eLogWarning, "DNNP: Pending tunnel for message ", replyMsgID, " not found");
}
std::shared_ptr<DNNPMessage> CreateTunnelDataMsg (const uint8_t * buf)
{
auto msg = NewDNNPTunnelMessage ();
msg->Concat (buf, dotnet::tunnel::TUNNEL_DATA_MSG_SIZE);
msg->FillDNNPMessageHeader (eDNNPTunnelData);
return msg;
}
std::shared_ptr<DNNPMessage> CreateTunnelDataMsg (uint32_t tunnelID, const uint8_t * payload)
{
auto msg = NewDNNPTunnelMessage ();
htobe32buf (msg->GetPayload (), tunnelID);
msg->len += 4; // tunnelID
msg->Concat (payload, dotnet::tunnel::TUNNEL_DATA_MSG_SIZE - 4);
msg->FillDNNPMessageHeader (eDNNPTunnelData);
return msg;
}
std::shared_ptr<DNNPMessage> CreateEmptyTunnelDataMsg ()
{
auto msg = NewDNNPTunnelMessage ();
msg->len += dotnet::tunnel::TUNNEL_DATA_MSG_SIZE;
return msg;
}
std::shared_ptr<DNNPMessage> CreateTunnelGatewayMsg (uint32_t tunnelID, const uint8_t * buf, size_t len)
{
auto msg = NewDNNPMessage (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, "DNNP: tunnel gateway buffer overflow ", msg->maxLen);
msg->FillDNNPMessageHeader (eDNNPTunnelGateway);
return msg;
}
std::shared_ptr<DNNPMessage> CreateTunnelGatewayMsg (uint32_t tunnelID, std::shared_ptr<DNNPMessage> msg)
{
if (msg->offset >= DNNP_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 -= (DNNP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE);
msg->len = msg->offset + DNNP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE +len;
msg->FillDNNPMessageHeader (eDNNPTunnelGateway);
return msg;
}
else
return CreateTunnelGatewayMsg (tunnelID, msg->GetBuffer (), msg->GetLength ());
}
std::shared_ptr<DNNPMessage> CreateTunnelGatewayMsg (uint32_t tunnelID, DNNPMessageType msgType,
const uint8_t * buf, size_t len, uint32_t replyMsgID)
{
auto msg = NewDNNPMessage (len);
size_t gatewayMsgOffset = DNNP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE;
msg->offset += gatewayMsgOffset;
msg->len += gatewayMsgOffset;
if (msg->Concat (buf, len) < len)
LogPrint (eLogError, "DNNP: tunnel gateway buffer overflow ", msg->maxLen);
msg->FillDNNPMessageHeader (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->FillDNNPMessageHeader (eDNNPTunnelGateway); // gateway message
return msg;
}
size_t GetDNNPMessageLength (const uint8_t * msg, size_t len)
{
if (len < DNNP_HEADER_SIZE_OFFSET + 2)
{
LogPrint (eLogError, "DNNP: message length ", len, " is smaller than header");
return len;
}
auto l = bufbe16toh (msg + DNNP_HEADER_SIZE_OFFSET) + DNNP_HEADER_SIZE;
if (l > len)
{
LogPrint (eLogError, "DNNP: message length ", l, " exceeds buffer length ", len);
l = len;
}
return l;
}
void HandleDNNPMessage (uint8_t * msg, size_t len)
{
if (len < DNNP_HEADER_SIZE)
{
LogPrint (eLogError, "DNNP: message length ", len, " is smaller than header");
return;
}
uint8_t typeID = msg[DNNP_HEADER_TYPEID_OFFSET];
uint32_t msgID = bufbe32toh (msg + DNNP_HEADER_MSGID_OFFSET);
LogPrint (eLogDebug, "DNNP: msg received len=", len,", type=", (int)typeID, ", msgID=", (unsigned int)msgID);
uint8_t * buf = msg + DNNP_HEADER_SIZE;
auto size = bufbe16toh (msg + DNNP_HEADER_SIZE_OFFSET);
len -= DNNP_HEADER_SIZE;
if (size > len)
{
LogPrint (eLogError, "DNNP: payload size ", size, " exceeds buffer length ", len);
size = len;
}
switch (typeID)
{
case eDNNPVariableTunnelBuild:
HandleVariableTunnelBuildMsg (msgID, buf, size);
break;
case eDNNPVariableTunnelBuildReply:
HandleVariableTunnelBuildReplyMsg (msgID, buf, size);
break;
case eDNNPTunnelBuild:
HandleTunnelBuildMsg (buf, size);
break;
case eDNNPTunnelBuildReply:
// TODO:
break;
default:
LogPrint (eLogWarning, "DNNP: Unexpected message ", (int)typeID);
}
}
void HandleDNNPMessage (std::shared_ptr<DNNPMessage> msg)
{
if (msg)
{
uint8_t typeID = msg->GetTypeID ();
LogPrint (eLogDebug, "DNNP: Handling message with type ", (int)typeID);
switch (typeID)
{
case eDNNPTunnelData:
dotnet::tunnel::tunnels.PostTunnelData (msg);
break;
case eDNNPTunnelGateway:
dotnet::tunnel::tunnels.PostTunnelData (msg);
break;
case eDNNPGarlic:
{
if (msg->from)
{
if (msg->from->GetTunnelPool ())
msg->from->GetTunnelPool ()->ProcessGarlicMessage (msg);
else
LogPrint (eLogInfo, "DNNP: Local destination for garlic doesn't exist anymore");
}
else
dotnet::context.ProcessGarlicMessage (msg);
break;
}
case eDNNPDatabaseStore:
case eDNNPDatabaseSearchReply:
case eDNNPDatabaseLookup:
// forward to netDb
dotnet::data::netdb.PostDNNPMsg (msg);
break;
case eDNNPDeliveryStatus:
{
if (msg->from && msg->from->GetTunnelPool ())
msg->from->GetTunnelPool ()->ProcessDeliveryStatus (msg);
else
dotnet::context.ProcessDeliveryStatusMessage (msg);
break;
}
case eDNNPVariableTunnelBuild:
case eDNNPVariableTunnelBuildReply:
case eDNNPTunnelBuild:
case eDNNPTunnelBuildReply:
// forward to tunnel thread
dotnet::tunnel::tunnels.PostTunnelData (msg);
break;
default:
HandleDNNPMessage (msg->GetBuffer (), msg->GetLength ());
}
}
}
DNNPMessagesHandler::~DNNPMessagesHandler ()
{
Flush ();
}
void DNNPMessagesHandler::PutNextMessage (std::shared_ptr<DNNPMessage> msg)
{
if (msg)
{
switch (msg->GetTypeID ())
{
case eDNNPTunnelData:
m_TunnelMsgs.push_back (msg);
break;
case eDNNPTunnelGateway:
m_TunnelGatewayMsgs.push_back (msg);
break;
default:
HandleDNNPMessage (msg);
}
}
}
void DNNPMessagesHandler::Flush ()
{
if (!m_TunnelMsgs.empty ())
{
dotnet::tunnel::tunnels.PostTunnelData (m_TunnelMsgs);
m_TunnelMsgs.clear ();
}
if (!m_TunnelGatewayMsgs.empty ())
{
dotnet::tunnel::tunnels.PostTunnelData (m_TunnelGatewayMsgs);
m_TunnelGatewayMsgs.clear ();
}
}
}

289
libdotnet/DNNPProtocol.h Normal file
View file

@ -0,0 +1,289 @@
#ifndef DNNP_PROTOCOL_H__
#define DNNP_PROTOCOL_H__
#include <inttypes.h>
#include <string.h>
#include <set>
#include <memory>
#include "Crypto.h"
#include "DotNetEndian.h"
#include "Identity.h"
#include "RouterInfo.h"
#include "LeaseSet.h"
namespace dotnet
{
// DNNP header
const size_t DNNP_HEADER_TYPEID_OFFSET = 0;
const size_t DNNP_HEADER_MSGID_OFFSET = DNNP_HEADER_TYPEID_OFFSET + 1;
const size_t DNNP_HEADER_EXPIRATION_OFFSET = DNNP_HEADER_MSGID_OFFSET + 4;
const size_t DNNP_HEADER_SIZE_OFFSET = DNNP_HEADER_EXPIRATION_OFFSET + 8;
const size_t DNNP_HEADER_CHKS_OFFSET = DNNP_HEADER_SIZE_OFFSET + 2;
const size_t DNNP_HEADER_SIZE = DNNP_HEADER_CHKS_OFFSET + 1;
// DNNP short header
const size_t DNNP_SHORT_HEADER_TYPEID_OFFSET = 0;
const size_t DNNP_SHORT_HEADER_EXPIRATION_OFFSET = DNNP_SHORT_HEADER_TYPEID_OFFSET + 1;
const size_t DNNP_SHORT_HEADER_SIZE = DNNP_SHORT_HEADER_EXPIRATION_OFFSET + 4;
// DNNP NTCP2 header
const size_t DNNP_NTCP2_HEADER_SIZE = DNNP_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 DNNPMessageType
{
eDNNPDummyMsg = 0,
eDNNPDatabaseStore = 1,
eDNNPDatabaseLookup = 2,
eDNNPDatabaseSearchReply = 3,
eDNNPDeliveryStatus = 10,
eDNNPGarlic = 11,
eDNNPTunnelData = 18,
eDNNPTunnelGateway = 19,
eDNNPData = 20,
eDNNPTunnelBuild = 21,
eDNNPTunnelBuildReply = 22,
eDNNPVariableTunnelBuild = 23,
eDNNPVariableTunnelBuildReply = 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 DNNP_MAX_MESSAGE_SIZE = 62708;
const size_t DNNP_MAX_SHORT_MESSAGE_SIZE = 4096;
const unsigned int DNNP_MESSAGE_EXPIRATION_TIMEOUT = 8000; // in milliseconds (as initial RTT)
const unsigned int DNNP_MESSAGE_CLOCK_SKEW = 60*1000; // 1 minute in milliseconds
struct DNNPMessage
{
uint8_t * buf;
size_t len, offset, maxLen;
std::shared_ptr<dotnet::tunnel::InboundTunnel> from;
DNNPMessage (): buf (nullptr),len (DNNP_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 ()[DNNP_HEADER_TYPEID_OFFSET] = typeID; };
uint8_t GetTypeID () const { return GetHeader ()[DNNP_HEADER_TYPEID_OFFSET]; };
void SetMsgID (uint32_t msgID) { htobe32buf (GetHeader () + DNNP_HEADER_MSGID_OFFSET, msgID); };
uint32_t GetMsgID () const { return bufbe32toh (GetHeader () + DNNP_HEADER_MSGID_OFFSET); };
void SetExpiration (uint64_t expiration) { htobe64buf (GetHeader () + DNNP_HEADER_EXPIRATION_OFFSET, expiration); };
uint64_t GetExpiration () const { return bufbe64toh (GetHeader () + DNNP_HEADER_EXPIRATION_OFFSET); };
void SetSize (uint16_t size) { htobe16buf (GetHeader () + DNNP_HEADER_SIZE_OFFSET, size); };
uint16_t GetSize () const { return bufbe16toh (GetHeader () + DNNP_HEADER_SIZE_OFFSET); };
void UpdateSize () { SetSize (GetPayloadLength ()); };
void SetChks (uint8_t chks) { GetHeader ()[DNNP_HEADER_CHKS_OFFSET] = chks; };
void UpdateChks ()
{
uint8_t hash[32];
SHA256(GetPayload (), GetPayloadLength (), hash);
GetHeader ()[DNNP_HEADER_CHKS_OFFSET] = hash[0];
}
// payload
uint8_t * GetPayload () { return GetBuffer () + DNNP_HEADER_SIZE; };
const uint8_t * GetPayload () const { return GetBuffer () + DNNP_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 () - DNNP_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;
}
DNNPMessage& operator=(const DNNPMessage& 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 + DNNP_HEADER_SIZE - DNNP_SHORT_HEADER_SIZE; };
void FromSSU (uint32_t msgID) // we have received SSU message and convert it to regular
{
const uint8_t * ssu = GetSSUHeader ();
GetHeader ()[DNNP_HEADER_TYPEID_OFFSET] = ssu[DNNP_SHORT_HEADER_TYPEID_OFFSET]; // typeid
SetMsgID (msgID);
SetExpiration (bufbe32toh (ssu + DNNP_SHORT_HEADER_EXPIRATION_OFFSET)*1000LL);
SetSize (len - offset - DNNP_HEADER_SIZE);
SetChks (0);
}
uint32_t ToSSU () // return msgID
{
uint8_t header[DNNP_HEADER_SIZE];
memcpy (header, GetHeader (), DNNP_HEADER_SIZE);
uint8_t * ssu = GetSSUHeader ();
ssu[DNNP_SHORT_HEADER_TYPEID_OFFSET] = header[DNNP_HEADER_TYPEID_OFFSET]; // typeid
htobe32buf (ssu + DNNP_SHORT_HEADER_EXPIRATION_OFFSET, bufbe64toh (header + DNNP_HEADER_EXPIRATION_OFFSET)/1000LL);
len = offset + DNNP_SHORT_HEADER_SIZE + bufbe16toh (header + DNNP_HEADER_SIZE_OFFSET);
return bufbe32toh (header + DNNP_HEADER_MSGID_OFFSET);
}
// for NTCP2 only
uint8_t * GetNTCP2Header () { return GetPayload () - DNNP_NTCP2_HEADER_SIZE; };
size_t GetNTCP2Length () const { return GetPayloadLength () + DNNP_NTCP2_HEADER_SIZE; };
void FromNTCP2 ()
{
const uint8_t * ntcp2 = GetNTCP2Header ();
memcpy (GetHeader () + DNNP_HEADER_TYPEID_OFFSET, ntcp2 + DNNP_HEADER_TYPEID_OFFSET, 5); // typeid + msgid
SetExpiration (bufbe32toh (ntcp2 + DNNP_HEADER_EXPIRATION_OFFSET)*1000LL);
SetSize (len - offset - DNNP_HEADER_SIZE);
SetChks (0);
}
void ToNTCP2 ()
{
uint8_t * ntcp2 = GetNTCP2Header ();
htobe32buf (ntcp2 + DNNP_HEADER_EXPIRATION_OFFSET, bufbe64toh (GetHeader () + DNNP_HEADER_EXPIRATION_OFFSET)/1000LL);
memcpy (ntcp2 + DNNP_HEADER_TYPEID_OFFSET, GetHeader () + DNNP_HEADER_TYPEID_OFFSET, 5); // typeid + msgid
}
void FillDNNPMessageHeader (DNNPMessageType msgType, uint32_t replyMsgID = 0);
void RenewDNNPMessageHeader ();
bool IsExpired () const;
};
template<int sz>
struct DNNPMessageBuffer: public DNNPMessage
{
DNNPMessageBuffer () { buf = m_Buffer; maxLen = sz; };
uint8_t m_Buffer[sz + 32]; // 16 alignment + 16 padding
};
std::shared_ptr<DNNPMessage> NewDNNPMessage ();
std::shared_ptr<DNNPMessage> NewDNNPShortMessage ();
std::shared_ptr<DNNPMessage> NewDNNPTunnelMessage ();
std::shared_ptr<DNNPMessage> NewDNNPMessage (size_t len);
std::shared_ptr<DNNPMessage> CreateDNNPMessage (DNNPMessageType msgType, const uint8_t * buf, size_t len, uint32_t replyMsgID = 0);
std::shared_ptr<DNNPMessage> CreateDNNPMessage (const uint8_t * buf, size_t len, std::shared_ptr<dotnet::tunnel::InboundTunnel> from = nullptr);
std::shared_ptr<DNNPMessage> CopyDNNPMessage (std::shared_ptr<DNNPMessage> msg);
std::shared_ptr<DNNPMessage> CreateDeliveryStatusMsg (uint32_t msgID);
std::shared_ptr<DNNPMessage> CreateRouterInfoDatabaseLookupMsg (const uint8_t * key, const uint8_t * from,
uint32_t replyTunnelID, bool exploratory = false, std::set<dotnet::data::IdentHash> * excludedPeers = nullptr);
std::shared_ptr<DNNPMessage> CreateLeaseSetDatabaseLookupMsg (const dotnet::data::IdentHash& dest,
const std::set<dotnet::data::IdentHash>& excludedFloodfills,
std::shared_ptr<const dotnet::tunnel::InboundTunnel> replyTunnel, const uint8_t * replyKey, const uint8_t * replyTag);
std::shared_ptr<DNNPMessage> CreateDatabaseSearchReply (const dotnet::data::IdentHash& ident, std::vector<dotnet::data::IdentHash> routers);
std::shared_ptr<DNNPMessage> CreateDatabaseStoreMsg (std::shared_ptr<const dotnet::data::RouterInfo> router = nullptr, uint32_t replyToken = 0);
std::shared_ptr<DNNPMessage> CreateDatabaseStoreMsg (std::shared_ptr<const dotnet::data::LeaseSet> leaseSet); // for floodfill only
std::shared_ptr<DNNPMessage> CreateDatabaseStoreMsg (std::shared_ptr<const dotnet::data::LocalLeaseSet> leaseSet, uint32_t replyToken = 0, std::shared_ptr<const dotnet::tunnel::InboundTunnel> replyTunnel = nullptr);
bool IsRouterInfoMsg (std::shared_ptr<DNNPMessage> 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<DNNPMessage> CreateTunnelDataMsg (const uint8_t * buf);
std::shared_ptr<DNNPMessage> CreateTunnelDataMsg (uint32_t tunnelID, const uint8_t * payload);
std::shared_ptr<DNNPMessage> CreateEmptyTunnelDataMsg ();
std::shared_ptr<DNNPMessage> CreateTunnelGatewayMsg (uint32_t tunnelID, const uint8_t * buf, size_t len);
std::shared_ptr<DNNPMessage> CreateTunnelGatewayMsg (uint32_t tunnelID, DNNPMessageType msgType,
const uint8_t * buf, size_t len, uint32_t replyMsgID = 0);
std::shared_ptr<DNNPMessage> CreateTunnelGatewayMsg (uint32_t tunnelID, std::shared_ptr<DNNPMessage> msg);
size_t GetDNNPMessageLength (const uint8_t * msg, size_t len);
void HandleDNNPMessage (uint8_t * msg, size_t len);
void HandleDNNPMessage (std::shared_ptr<DNNPMessage> msg);
class DNNPMessagesHandler
{
public:
~DNNPMessagesHandler ();
void PutNextMessage (std::shared_ptr<DNNPMessage> msg);
void Flush ();
private:
std::vector<std::shared_ptr<DNNPMessage> > m_TunnelMsgs, m_TunnelGatewayMsgs;
};
const uint16_t DEFAULT_MAX_NUM_TRANSIT_TUNNELS = 2500;
void SetMaxNumTransitTunnels (uint16_t maxNumTransitTunnels);
}
#endif

369
libdotnet/Datagram.cpp Normal file
View 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 dotnet
{
namespace datagram
{
DatagramDestination::DatagramDestination (std::shared_ptr<dotnet::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 dotnet::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 () == dotnet::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)
{
dotnet::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 () == dotnet::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<DNNPMessage> DatagramDestination::CreateDataMessage (const uint8_t * payload, size_t len, uint16_t fromPort, uint16_t toPort)
{
auto msg = NewDNNPMessage ();
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] = dotnet::client::PROTOCOL_TYPE_DATAGRAM; // datagram protocol
msg->len += size + 4;
msg->FillDNNPMessageHeader (eDNNPData);
}
else
msg = nullptr;
return msg;
}
void DatagramDestination::CleanUp ()
{
if (m_Sessions.empty ()) return;
auto now = dotnet::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 dotnet::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 dotnet::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(dotnet::client::ClientDestination * localDestination,
const dotnet::data::IdentHash & remoteIdent) :
m_LocalDestination(localDestination),
m_RemoteIdent(remoteIdent),
m_SendQueueTimer(localDestination->GetService()),
m_RequestingLS(false)
{
}
void DatagramSession::Start ()
{
m_LastUse = dotnet::util::GetMillisecondsSinceEpoch ();
ScheduleFlushSendQueue();
}
void DatagramSession::Stop ()
{
m_SendQueueTimer.cancel ();
}
void DatagramSession::SendMsg(std::shared_ptr<DNNPMessage> msg)
{
// we used this session
m_LastUse = dotnet::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 = dotnet::util::GetMillisecondsSinceEpoch();
auto path = GetSharedRoutingPath();
if(path)
path->updateTime = dotnet::util::GetSecondsSinceEpoch ();
}
std::shared_ptr<dotnet::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 dotnet::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<dotnet::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 dotnet::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<dotnet::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<DNNPMessage> 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<dotnet::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(dotnet::tunnel::TunnelMessageBlock{dotnet::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
libdotnet/Datagram.h Normal file
View 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 "DNNPProtocol.h"
#include "Garlic.h"
namespace dotnet
{
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(dotnet::client::ClientDestination * localDestination, const dotnet::data::IdentHash & remoteIdent);
void Start ();
void Stop ();
/** @brief ack the garlic routing path */
void Ack();
/** send an dnnp message to remote endpoint for this session */
void SendMsg(std::shared_ptr<DNNPMessage> 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 dotnet::data::IdentHash> IBGW;
std::shared_ptr<const dotnet::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<dotnet::data::IdentHash>(ibgw);
else IBGW = nullptr;
if(obep) OBEP = std::make_shared<dotnet::data::IdentHash>(obep);
else OBEP = nullptr;
}
};
Info GetSessionInfo() const;
private:
void FlushSendQueue();
void ScheduleFlushSendQueue();
void HandleSend(std::shared_ptr<DNNPMessage> msg);
std::shared_ptr<dotnet::garlic::GarlicRoutingPath> GetSharedRoutingPath();
void HandleLeaseSetUpdated(std::shared_ptr<dotnet::data::LeaseSet> ls);
private:
dotnet::client::ClientDestination * m_LocalDestination;
dotnet::data::IdentHash m_RemoteIdent;
std::shared_ptr<const dotnet::data::LeaseSet> m_RemoteLeaseSet;
std::shared_ptr<dotnet::garlic::GarlicRoutingSession> m_RoutingSession;
std::shared_ptr<const dotnet::data::Lease> m_CurrentRemoteLease;
std::shared_ptr<dotnet::tunnel::OutboundTunnel> m_CurrentOutboundTunnel;
boost::asio::deadline_timer m_SendQueueTimer;
std::vector<std::shared_ptr<DNNPMessage> > 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 dotnet::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len)> Receiver;
public:
DatagramDestination (std::shared_ptr<dotnet::client::ClientDestination> owner);
~DatagramDestination ();
void SendDatagramTo (const uint8_t * payload, size_t len, const dotnet::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 dotnet::data::IdentHash & remote);
// clean up stale sessions
void CleanUp ();
private:
std::shared_ptr<DatagramSession> ObtainSession(const dotnet::data::IdentHash & ident);
std::shared_ptr<DNNPMessage> 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:
dotnet::client::ClientDestination * m_Owner;
dotnet::data::IdentityEx m_Identity;
Receiver m_Receiver; // default
std::mutex m_SessionsMutex;
std::map<dotnet::data::IdentHash, DatagramSession_ptr > m_Sessions;
std::mutex m_ReceiversMutex;
std::map<uint16_t, Receiver> m_ReceiversByPorts;
dotnet::data::GzipInflator m_Inflator;
dotnet::data::GzipDeflator m_Deflator;
};
}
}
#endif

1155
libdotnet/Destination.cpp Normal file

File diff suppressed because it is too large Load diff

271
libdotnet/Destination.h Normal file
View file

@ -0,0 +1,271 @@
#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.hpp"
#include "Streaming.h"
#include "Datagram.h"
namespace dotnet
{
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 successful 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;
// DNCP
const char DNCP_PARAM_INBOUND_TUNNEL_LENGTH[] = "inbound.length";
const int DEFAULT_INBOUND_TUNNEL_LENGTH = 3;
const char DNCP_PARAM_OUTBOUND_TUNNEL_LENGTH[] = "outbound.length";
const int DEFAULT_OUTBOUND_TUNNEL_LENGTH = 3;
const char DNCP_PARAM_INBOUND_TUNNELS_QUANTITY[] = "inbound.quantity";
const int DEFAULT_INBOUND_TUNNELS_QUANTITY = 5;
const char DNCP_PARAM_OUTBOUND_TUNNELS_QUANTITY[] = "outbound.quantity";
const int DEFAULT_OUTBOUND_TUNNELS_QUANTITY = 5;
const char DNCP_PARAM_EXPLICIT_PEERS[] = "explicitPeers";
const int STREAM_REQUEST_TIMEOUT = 60; //in seconds
const char DNCP_PARAM_TAGS_TO_SEND[] = "crypto.tagsToSend";
const int DEFAULT_TAGS_TO_SEND = 40;
const char DNCP_PARAM_INBOUND_NICKNAME[] = "inbound.nickname";
const char DNCP_PARAM_OUTBOUND_NICKNAME[] = "outbound.nickname";
const char DNCP_PARAM_LEASESET_TYPE[] = "dncp.leaseSetType";
const int DEFAULT_LEASESET_TYPE = 1;
const char DNCP_PARAM_LEASESET_ENCRYPTION_TYPE[] = "dncp.leaseSetEncType";
// latency
const char DNCP_PARAM_MIN_TUNNEL_LATENCY[] = "latency.min";
const int DEFAULT_MIN_TUNNEL_LATENCY = 0;
const char DNCP_PARAM_MAX_TUNNEL_LATENCY[] = "latency.max";
const int DEFAULT_MAX_TUNNEL_LATENCY = 0;
// streaming
const char DNCP_PARAM_STREAMING_INITIAL_ACK_DELAY[] = "dotnet.streaming.initialAckDelay";
const int DEFAULT_INITIAL_ACK_DELAY = 200; // milliseconds
typedef std::function<void (std::shared_ptr<dotnet::stream::Stream> stream)> StreamRequestComplete;
class LeaseSetDestination: public dotnet::garlic::GarlicDestination,
public std::enable_shared_from_this<LeaseSetDestination>
{
typedef std::function<void (std::shared_ptr<dotnet::data::LeaseSet> leaseSet)> RequestComplete;
// leaseSet = nullptr means not found
struct LeaseSetRequest
{
LeaseSetRequest (boost::asio::io_service& service): requestTime (0), requestTimeoutTimer (service) {};
std::set<dotnet::data::IdentHash> excluded;
uint64_t requestTime;
boost::asio::deadline_timer requestTimeoutTimer;
std::list<RequestComplete> requestComplete;
std::shared_ptr<dotnet::tunnel::OutboundTunnel> outboundTunnel;
std::shared_ptr<dotnet::tunnel::InboundTunnel> replyTunnel;
std::shared_ptr<const dotnet::data::BlindedPublicKey> requestedBlindedKey; // for encrypted LeaseSet2 only
void Complete (std::shared_ptr<dotnet::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 ();
const std::string& GetNickname () const { return m_Nickname; };
virtual bool Start ();
virtual bool Stop ();
/** dncp reconfigure */
virtual bool Reconfigure(std::map<std::string, std::string> dncpOpts);
bool IsRunning () const { return m_IsRunning; };
boost::asio::io_service& GetService () { return m_Service; };
std::shared_ptr<dotnet::tunnel::TunnelPool> GetTunnelPool () { return m_Pool; };
bool IsReady () const { return m_LeaseSet && !m_LeaseSet->IsExpired () && m_Pool->GetOutboundTunnels ().size () > 0; };
std::shared_ptr<dotnet::data::LeaseSet> FindLeaseSet (const dotnet::data::IdentHash& ident);
bool RequestDestination (const dotnet::data::IdentHash& dest, RequestComplete requestComplete = nullptr);
bool RequestDestinationWithEncryptedLeaseSet (std::shared_ptr<const dotnet::data::BlindedPublicKey> dest, RequestComplete requestComplete = nullptr);
void CancelDestinationRequest (const dotnet::data::IdentHash& dest, bool notify = true);
void CancelDestinationRequestWithEncryptedLeaseSet (std::shared_ptr<const dotnet::data::BlindedPublicKey> dest, bool notify = true);
// implements GarlicDestination
std::shared_ptr<const dotnet::data::LocalLeaseSet> GetLeaseSet ();
std::shared_ptr<dotnet::tunnel::TunnelPool> GetTunnelPool () const { return m_Pool; }
void HandleDNNPMessage (const uint8_t * buf, size_t len, std::shared_ptr<dotnet::tunnel::InboundTunnel> from);
// override GarlicDestination
bool SubmitSessionKey (const uint8_t * key, const uint8_t * tag);
void ProcessGarlicMessage (std::shared_ptr<DNNPMessage> msg);
void ProcessDeliveryStatusMessage (std::shared_ptr<DNNPMessage> msg);
void SetLeaseSetUpdated ();
protected:
void SetLeaseSet (std::shared_ptr<const dotnet::data::LocalLeaseSet> newLeaseSet);
int GetLeaseSetType () const { return m_LeaseSetType; };
void SetLeaseSetType (int leaseSetType) { m_LeaseSetType = leaseSetType; };
virtual void CleanupDestination () {}; // additional clean up in derived classes
// DNCP
virtual void HandleDataMessage (const uint8_t * buf, size_t len) = 0;
virtual void CreateNewLeaseSet (std::vector<std::shared_ptr<dotnet::tunnel::InboundTunnel> > tunnels) = 0;
private:
void Run ();
void UpdateLeaseSet ();
std::shared_ptr<const dotnet::data::LocalLeaseSet> GetLeaseSetMt ();
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<DNNPMessage> msg);
void RequestLeaseSet (const dotnet::data::IdentHash& dest, RequestComplete requestComplete, std::shared_ptr<const dotnet::data::BlindedPublicKey> requestedBlindedKey = nullptr);
bool SendLeaseSetRequest (const dotnet::data::IdentHash& dest, std::shared_ptr<const dotnet::data::RouterInfo> nextFloodfill, std::shared_ptr<LeaseSetRequest> request);
void HandleRequestTimoutTimer (const boost::system::error_code& ecode, const dotnet::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<dotnet::data::IdentHash, std::shared_ptr<dotnet::data::LeaseSet> > m_RemoteLeaseSets;
std::map<dotnet::data::IdentHash, std::shared_ptr<LeaseSetRequest> > m_LeaseSetRequests;
std::shared_ptr<dotnet::tunnel::TunnelPool> m_Pool;
std::mutex m_LeaseSetMutex;
std::shared_ptr<const dotnet::data::LocalLeaseSet> m_LeaseSet;
bool m_IsPublic;
uint32_t m_PublishReplyToken;
uint64_t m_LastSubmissionTime; // in seconds
std::set<dotnet::data::IdentHash> m_ExcludedFloodfills; // for publishing
boost::asio::deadline_timer m_PublishConfirmationTimer, m_PublishVerificationTimer,
m_PublishDelayTimer, m_CleanupTimer;
std::string m_Nickname;
int m_LeaseSetType;
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 dotnet::data::PrivateKeys& keys, bool isPublic, const std::map<std::string, std::string> * params = nullptr);
~ClientDestination ();
virtual bool Start ();
virtual bool Stop ();
const dotnet::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); };
// ref counter
int Acquire () { return ++m_RefCounter; };
int Release () { return --m_RefCounter; };
int GetRefCounter () const { return m_RefCounter; };
// streaming
std::shared_ptr<dotnet::stream::StreamingDestination> CreateStreamingDestination (int port, bool gzip = true); // additional
std::shared_ptr<dotnet::stream::StreamingDestination> GetStreamingDestination (int port = 0) const;
// following methods operate with default streaming destination
void CreateStream (StreamRequestComplete streamRequestComplete, const dotnet::data::IdentHash& dest, int port = 0);
void CreateStream (StreamRequestComplete streamRequestComplete, std::shared_ptr<const dotnet::data::BlindedPublicKey> dest, int port = 0);
std::shared_ptr<dotnet::stream::Stream> CreateStream (std::shared_ptr<const dotnet::data::LeaseSet> remote, int port = 0);
void AcceptStreams (const dotnet::stream::StreamingDestination::Acceptor& acceptor);
void StopAcceptingStreams ();
bool IsAcceptingStreams () const;
void AcceptOnce (const dotnet::stream::StreamingDestination::Acceptor& acceptor);
int GetStreamingAckDelay () const { return m_StreamingAckDelay; }
// datagram
dotnet::datagram::DatagramDestination * GetDatagramDestination () const { return m_DatagramDestination; };
dotnet::datagram::DatagramDestination * CreateDatagramDestination ();
// implements LocalDestination
bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) const;
std::shared_ptr<const dotnet::data::IdentityEx> GetIdentity () const { return m_Keys.GetPublic (); };
protected:
void CleanupDestination ();
// DNCP
void HandleDataMessage (const uint8_t * buf, size_t len);
void CreateNewLeaseSet (std::vector<std::shared_ptr<dotnet::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:
dotnet::data::PrivateKeys m_Keys;
uint8_t m_EncryptionPublicKey[256], m_EncryptionPrivateKey[256];
dotnet::data::CryptoKeyType m_EncryptionKeyType;
std::shared_ptr<dotnet::crypto::CryptoKeyDecryptor> m_Decryptor;
int m_StreamingAckDelay;
std::shared_ptr<dotnet::stream::StreamingDestination> m_StreamingDestination; // default
std::map<uint16_t, std::shared_ptr<dotnet::stream::StreamingDestination> > m_StreamingDestinationsByPorts;
dotnet::datagram::DatagramDestination * m_DatagramDestination;
int m_RefCounter; // how many clients(tunnels) use this destination
boost::asio::deadline_timer m_ReadyChecker;
public:
// for HTTP only
std::vector<std::shared_ptr<const dotnet::stream::Stream> > GetAllStreams () const;
};
}
}
#endif

View file

@ -0,0 +1,83 @@
#include "DotNetEndian.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;
}
*/

132
libdotnet/DotNetEndian.h Normal file
View file

@ -0,0 +1,132 @@
#ifndef DOTNETENDIAN_H__
#define DOTNETENDIAN_H__
#include <inttypes.h>
#include <string.h>
#if defined(__FreeBSD__) || defined(__NetBSD__)
#include <sys/endian.h>
#elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__OpenBSD__) || defined(__GLIBC__)
#include <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));
}
inline void htole16buf(void *buf, uint16_t big16)
{
htobuf16(buf, htole16(big16));
}
inline void htole32buf(void *buf, uint32_t big32)
{
htobuf32(buf, htole32(big32));
}
inline void htole64buf(void *buf, uint64_t big64)
{
htobuf64(buf, htole64(big64));
}
#endif // DOTNETENDIAN_H__

600
libdotnet/Ed25519.cpp Normal file
View file

@ -0,0 +1,600 @@
#include <openssl/sha.h>
#include "Log.h"
#include "Crypto.h"
#include "Ed25519.h"
namespace dotnet
{
namespace crypto
{
Ed25519::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::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::~Ed25519 ()
{
BN_free (q);
BN_free (l);
BN_free (d);
BN_free (I);
BN_free (two_252_2);
}
EDDSAPoint Ed25519::GeneratePublicKey (const uint8_t * expandedPrivateKey, BN_CTX * ctx) const
{
return MulB (expandedPrivateKey, ctx); // left half of expanded key, considered as Little Endian
}
EDDSAPoint Ed25519::DecodePublicKey (const uint8_t * buf, BN_CTX * ctx) const
{
return DecodePoint (buf, ctx);
}
void Ed25519::EncodePublicKey (const EDDSAPoint& publicKey, uint8_t * buf, BN_CTX * ctx) const
{
EncodePoint (Normalize (publicKey, ctx), buf);
}
bool Ed25519::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 Ed25519::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);
}
void Ed25519::SignRedDSA (const uint8_t * privateKey, const uint8_t * publicKeyEncoded,
const uint8_t * buf, size_t len, uint8_t * signature) const
{
BN_CTX * bnCtx = BN_CTX_new ();
// T = 80 random bytes
uint8_t T[80];
RAND_bytes (T, 80);
// calculate r = H*(T || publickey || data)
SHA512_CTX ctx;
SHA512_Init (&ctx);
SHA512_Update (&ctx, T, 80);
SHA512_Update (&ctx, publicKeyEncoded, 32);
SHA512_Update (&ctx, buf, len); // data
uint8_t digest[64];
SHA512_Final (digest, &ctx);
BIGNUM * r = DecodeBN<64> (digest);
BN_mod (r, r, l, bnCtx); // % l
EncodeBN (r, digest, 32);
// 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);
// 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> (privateKey);
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);
}
EDDSAPoint Ed25519::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 Ed25519::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 Ed25519::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 Ed25519::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 Ed25519::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 Ed25519::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 * Ed25519::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);
BN_CTX_end (ctx);
return x;
}
EDDSAPoint Ed25519::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);
BIGNUM * 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 Ed25519::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 * Ed25519::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 Ed25519::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;
}
}
#if !OPENSSL_X25519
BIGNUM * Ed25519::ScalarMul (const BIGNUM * u, const BIGNUM * k, BN_CTX * ctx) const
{
BN_CTX_start (ctx);
auto x1 = BN_CTX_get (ctx); BN_copy (x1, u);
auto x2 = BN_CTX_get (ctx); BN_one (x2);
auto z2 = BN_CTX_get (ctx); BN_zero (z2);
auto x3 = BN_CTX_get (ctx); BN_copy (x3, u);
auto z3 = BN_CTX_get (ctx); BN_one (z3);
auto c121666 = BN_CTX_get (ctx); BN_set_word (c121666, 121666);
auto tmp0 = BN_CTX_get (ctx); auto tmp1 = BN_CTX_get (ctx);
unsigned int swap = 0;
auto bits = BN_num_bits (k);
while(bits)
{
--bits;
auto k_t = BN_is_bit_set(k, bits) ? 1 : 0;
swap ^= k_t;
if (swap)
{
std::swap (x2, x3);
std::swap (z2, z3);
}
swap = k_t;
BN_mod_sub(tmp0, x3, z3, q, ctx);
BN_mod_sub(tmp1, x2, z2, q, ctx);
BN_mod_add(x2, x2, z2, q, ctx);
BN_mod_add(z2, x3, z3, q, ctx);
BN_mod_mul(z3, tmp0, x2, q, ctx);
BN_mod_mul(z2, z2, tmp1, q, ctx);
BN_mod_sqr(tmp0, tmp1, q, ctx);
BN_mod_sqr(tmp1, x2, q, ctx);
BN_mod_add(x3, z3, z2, q, ctx);
BN_mod_sub(z2, z3, z2, q, ctx);
BN_mod_mul(x2, tmp1, tmp0, q, ctx);
BN_mod_sub(tmp1, tmp1, tmp0, q, ctx);
BN_mod_sqr(z2, z2, q, ctx);
BN_mod_mul(z3, tmp1, c121666, q, ctx);
BN_mod_sqr(x3, x3, q, ctx);
BN_mod_add(tmp0, tmp0, z3, q, ctx);
BN_mod_mul(z3, x1, z2, q, ctx);
BN_mod_mul(z2, tmp1, tmp0, q, ctx);
}
if (swap)
{
std::swap (x2, x3);
std::swap (z2, z3);
}
BN_mod_inverse (z2, z2, q, ctx);
BIGNUM * res = BN_new (); // not from ctx
BN_mod_mul(res, x2, z2, q, ctx);
BN_CTX_end (ctx);
return res;
}
void Ed25519::ScalarMul (const uint8_t * p, const uint8_t * e, uint8_t * buf, BN_CTX * ctx) const
{
BIGNUM * p1 = DecodeBN<32> (p);
uint8_t k[32];
memcpy (k, e, 32);
k[0] &= 248; k[31] &= 127; k[31] |= 64;
BIGNUM * n = DecodeBN<32> (k);
BIGNUM * q1 = ScalarMul (p1, n, ctx);
EncodeBN (q1, buf, 32);
BN_free (p1); BN_free (n); BN_free (q1);
}
void Ed25519::ScalarMulB (const uint8_t * e, uint8_t * buf, BN_CTX * ctx) const
{
BIGNUM *p1 = BN_new (); BN_set_word (p1, 9);
uint8_t k[32];
memcpy (k, e, 32);
k[0] &= 248; k[31] &= 127; k[31] |= 64;
BIGNUM * n = DecodeBN<32> (k);
BIGNUM * q1 = ScalarMul (p1, n, ctx);
EncodeBN (q1, buf, 32);
BN_free (p1); BN_free (n); BN_free (q1);
}
#endif
void Ed25519::BlindPublicKey (const uint8_t * pub, const uint8_t * seed, uint8_t * blinded)
{
BN_CTX * ctx = BN_CTX_new ();
// calculate alpha = seed mod l
BIGNUM * alpha = DecodeBN<64> (seed); // seed is in Little Endian
BN_mod (alpha, alpha, l, ctx); // % l
uint8_t priv[32];
EncodeBN (alpha, priv, 32); // back to Little Endian
BN_free (alpha);
// A' = BLIND_PUBKEY(A, alpha) = A + DERIVE_PUBLIC(alpha)
auto A1 = Sum (DecodePublicKey (pub, ctx), MulB (priv, ctx), ctx); // pub + B*alpha
EncodePublicKey (A1, blinded, ctx);
BN_CTX_free (ctx);
}
void Ed25519::BlindPrivateKey (const uint8_t * priv, const uint8_t * seed, uint8_t * blindedPriv, uint8_t * blindedPub)
{
BN_CTX * ctx = BN_CTX_new ();
// calculate alpha = seed mod l
BIGNUM * alpha = DecodeBN<64> (seed); // seed is in Little Endian
BN_mod (alpha, alpha, l, ctx); // % l
BIGNUM * p = DecodeBN<32> (priv); // priv is in Little Endian
BN_add (alpha, alpha, p); // alpha = alpha + priv
// a' = BLIND_PRIVKEY(a, alpha) = (a + alpha) mod L
BN_mod (alpha, alpha, l, ctx); // % l
EncodeBN (alpha, blindedPriv, 32);
// A' = DERIVE_PUBLIC(a')
auto A1 = MulB (blindedPriv, ctx);
EncodePublicKey (A1, blindedPub, ctx);
BN_free (alpha); BN_free (p);
BN_CTX_free (ctx);
}
void Ed25519::ExpandPrivateKey (const uint8_t * key, uint8_t * expandedKey)
{
SHA512 (key, EDDSA25519_PRIVATE_KEY_LENGTH, expandedKey);
expandedKey[0] &= 0xF8; // drop last 3 bits
expandedKey[EDDSA25519_PRIVATE_KEY_LENGTH - 1] &= 0x3F; // drop first 2 bits
expandedKey[EDDSA25519_PRIVATE_KEY_LENGTH - 1] |= 0x40; // set second bit
}
void Ed25519::CreateRedDSAPrivateKey (uint8_t * priv)
{
uint8_t seed[32];
RAND_bytes (seed, 32);
BIGNUM * p = DecodeBN<32> (seed);
BN_CTX * ctx = BN_CTX_new ();
BN_mod (p, p, l, ctx); // % l
EncodeBN (p, priv, 32);
BN_CTX_free (ctx);
BN_free (p);
}
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;
}
}
}

133
libdotnet/Ed25519.h Normal file
View file

@ -0,0 +1,133 @@
#ifndef ED25519_H__
#define ED25519_H__
#include <memory>
#include <openssl/bn.h>
#include "Crypto.h"
namespace dotnet
{
namespace crypto
{
struct EDDSAPoint
{
BIGNUM * x {nullptr};
BIGNUM * y {nullptr};
BIGNUM * z {nullptr};
BIGNUM * t {nullptr}; // projective coordinates
EDDSAPoint () {}
EDDSAPoint (const EDDSAPoint& other) { *this = other; }
EDDSAPoint (EDDSAPoint&& other) { *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 (this != &other)
{
BN_free (x); x = other.x; other.x = nullptr;
BN_free (y); y = other.y; other.y = nullptr;
BN_free (z); z = other.z; other.z = nullptr;
BN_free (t); t = other.t; other.t = nullptr;
}
return *this;
}
EDDSAPoint& operator=(const EDDSAPoint& other)
{
if (this != &other)
{
BN_free (x); x = other.x ? BN_dup (other.x) : nullptr;
BN_free (y); y = other.y ? BN_dup (other.y) : nullptr;
BN_free (z); z = other.z ? BN_dup (other.z) : nullptr;
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 Ed25519
{
public:
Ed25519 ();
Ed25519 (const Ed25519& other);
~Ed25519 ();
EDDSAPoint GeneratePublicKey (const uint8_t * expandedPrivateKey, BN_CTX * ctx) const;
EDDSAPoint DecodePublicKey (const uint8_t * buf, BN_CTX * ctx) const;
void EncodePublicKey (const EDDSAPoint& publicKey, uint8_t * buf, BN_CTX * ctx) const;
#if !OPENSSL_X25519
void ScalarMul (const uint8_t * p, const uint8_t * e, uint8_t * buf, BN_CTX * ctx) const; // p is point, e is number for x25519
void ScalarMulB (const uint8_t * e, uint8_t * buf, BN_CTX * ctx) const;
#endif
void BlindPublicKey (const uint8_t * pub, const uint8_t * seed, uint8_t * blinded); // for encrypted LeaseSet2, pub - 32, seed - 64, blinded - 32
void BlindPrivateKey (const uint8_t * priv, const uint8_t * seed, uint8_t * blindedPriv, uint8_t * blindedPub); // for encrypted LeaseSet2, pub - 32, seed - 64, blinded - 32
bool Verify (const EDDSAPoint& publicKey, const uint8_t * digest, const uint8_t * signature) const;
void Sign (const uint8_t * expandedPrivateKey, const uint8_t * publicKeyEncoded, const uint8_t * buf, size_t len, uint8_t * signature) const;
void SignRedDSA (const uint8_t * privateKey, const uint8_t * publicKeyEncoded, const uint8_t * buf, size_t len, uint8_t * signature) const;
static void ExpandPrivateKey (const uint8_t * key, uint8_t * expandedKey); // key - 32 bytes, expandedKey - 64 bytes
void CreateRedDSAPrivateKey (uint8_t * priv); // priv is 32 bytes
private:
EDDSAPoint Sum (const EDDSAPoint& p1, const EDDSAPoint& p2, BN_CTX * ctx) const;
void Double (EDDSAPoint& p, BN_CTX * ctx) const;
EDDSAPoint Mul (const EDDSAPoint& p, const BIGNUM * e, BN_CTX * ctx) const;
EDDSAPoint MulB (const uint8_t * e, BN_CTX * ctx) const; // B*e, e is 32 bytes Little Endian
EDDSAPoint Normalize (const EDDSAPoint& p, BN_CTX * ctx) const;
bool IsOnCurve (const EDDSAPoint& p, BN_CTX * ctx) const;
BIGNUM * RecoverX (const BIGNUM * y, BN_CTX * ctx) const;
EDDSAPoint DecodePoint (const uint8_t * buf, BN_CTX * ctx) const;
void EncodePoint (const EDDSAPoint& p, uint8_t * buf) const;
template<int len>
BIGNUM * DecodeBN (const uint8_t * buf) const;
void EncodeBN (const BIGNUM * bn, uint8_t * buf, size_t len) const;
#if !OPENSSL_X25519
// for x25519
BIGNUM * ScalarMul (const BIGNUM * p, const BIGNUM * e, BN_CTX * ctx) const;
#endif
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]
};
std::unique_ptr<Ed25519>& GetEd25519 ();
}
}
#endif

61
libdotnet/Event.cpp Normal file
View file

@ -0,0 +1,61 @@
#include "Event.h"
#include "Log.h"
namespace dotnet
{
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
dotnet::event::core.CollectEvent(type, ident, val);
#endif
}
void EmitEvent(const EventType & e)
{
#if WITH_EVENTS
dotnet::event::core.QueueEvent(e);
#endif
}

53
libdotnet/Event.h Normal file
View 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 dotnet
{
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

231
libdotnet/FS.cpp Normal file
View file

@ -0,0 +1,231 @@
/*
* Copyright (c) 2013-2016, The PurpleI2P Project
*
* This file is part of Purple dotnet 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>
#include <windows.h>
#endif
#include "Base.h"
#include "FS.h"
#include "Log.h"
#include "Garlic.h"
namespace dotnet {
namespace fs {
std::string appName = "dotnet";
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
if(!GetModuleFileName(NULL, localAppData, MAX_PATH))
{
#if defined(WIN32_APP)
MessageBox(NULL, TEXT("Unable to get application path!"), TEXT("DOTNET: error"), MB_ICONERROR | MB_OK);
#else
fprintf(stderr, "Error: Unable to get application path!");
#endif
exit(1);
}
else
{
auto execPath = boost::filesystem::path(localAppData).parent_path();
// if config file exists in .exe's folder use it
if(boost::filesystem::exists(execPath/"dotnet.conf")) // TODO: magic string
dataDir = execPath.string ();
else // otherwise %appdata%
{
if(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, localAppData) != S_OK)
{
#if defined(WIN32_APP)
MessageBox(NULL, TEXT("Unable to get AppData path!"), TEXT("DOTNET: error"), MB_ICONERROR | MB_OK);
#else
fprintf(stderr, "Error: Unable to get AppData path!");
#endif
exit(1);
}
else
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;
}
#endif
// otherwise use /data/files
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
dotnet::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 + dotnet::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 + dotnet::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 << dotnet::fs::dirSep;
t << prefix1 << safe_ident[0] << dotnet::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
} // dotnet

161
libdotnet/FS.h Normal file
View file

@ -0,0 +1,161 @@
/*
* Copyright (c) 2013-2016, The PurpleI2P Project
*
* This file is part of Purple dotnet 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 dotnet {
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 'dotnet' */
const std::string & GetAppName ();
/** @brief Set application 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\dotnet\
* Windows >= Vista: C:\Users\Username\AppData\Roaming\dotnet\
* Mac: /Library/Application Support/dotnet/ or ~/Library/Application Support/dotnet/
* Unix: /var/lib/dotnet/ (system=1) >> ~/.dotnet/ or /tmp/dotnet/
*/
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 << dotnet::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/dotnet"):
*
* dotnet::fs::Path("test") -> '/tmp/dotnet/test'
* dotnet::fs::Path("test", "file.txt") -> '/tmp/dotnet/test/file.txt'
*/
template<typename ... Other>
std::string DataDirPath(Other ... components) {
std::stringstream s("");
s << dotnet::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
} // dotnet
#endif // /* FS_H__ */

182
libdotnet/Family.cpp Normal file
View file

@ -0,0 +1,182 @@
#include <string.h>
#include <openssl/evp.h>
#include <openssl/ssl.h>
#include "Crypto.h"
#include "FS.h"
#include "Log.h"
#include "Family.h"
namespace dotnet
{
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<dotnet::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);
dotnet::crypto::bn2buf (x, signingKey, 32);
dotnet::crypto::bn2buf (y, signingKey + 32, 32);
BN_free (x); BN_free (y);
verifier = std::make_shared<dotnet::crypto::ECDSAP256Verifier>();
verifier->SetPublicKey (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 = dotnet::fs::DataDirPath("certificates", "family");
std::vector<std::string> files;
int numCertificates = 0;
if (!dotnet::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 = dotnet::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];
dotnet::crypto::bn2buf (EC_KEY_get0_private_key (ecKey), signingPrivateKey, 32);
dotnet::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
libdotnet/Family.h Normal file
View file

@ -0,0 +1,38 @@
#ifndef FAMILY_H__
#define FAMILY_H__
#include <map>
#include <string>
#include <memory>
#include "Signature.h"
#include "Identity.h"
namespace dotnet
{
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<dotnet::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

825
libdotnet/Garlic.cpp Normal file
View file

@ -0,0 +1,825 @@
#include <inttypes.h>
#include "DotNetEndian.h"
#include <map>
#include <string>
#include "Crypto.h"
#include "RouterContext.h"
#include "DNNPProtocol.h"
#include "Tunnel.h"
#include "TunnelPool.h"
#include "Transports.h"
#include "Timestamp.h"
#include "Log.h"
#include "FS.h"
#include "Garlic.h"
namespace dotnet
{
namespace garlic
{
GarlicRoutingSession::GarlicRoutingSession (GarlicDestination * owner,
std::shared_ptr<const dotnet::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 = dotnet::util::GetSecondsSinceEpoch ();
}
GarlicRoutingSession::~GarlicRoutingSession ()
{
}
std::shared_ptr<GarlicRoutingPath> GarlicRoutingSession::GetSharedRoutingPath ()
{
if (!m_SharedRoutingPath) return nullptr;
uint32_t ts = dotnet::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 = dotnet::util::GetSecondsSinceEpoch ();
path->numTimesUsed = 0;
}
else
path = nullptr;
m_SharedRoutingPath = path;
}
GarlicRoutingSession::UnconfirmedTags * GarlicRoutingSession::GenerateSessionTags ()
{
auto tags = new UnconfirmedTags (m_NumTags);
tags->tagsCreationTime = dotnet::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 = dotnet::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 = dotnet::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 = dotnet::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<DNNPMessage> GarlicRoutingSession::WrapSingleMessage (std::shared_ptr<const DNNPMessage> msg)
{
auto m = NewDNNPMessage ();
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 = dotnet::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 ();
m_Destination->Encrypt ((uint8_t *)&elGamal, buf, ctx);
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->FillDNNPMessageHeader (eDNNPGarlic);
return m;
}
size_t GarlicRoutingSession::CreateAESBlock (uint8_t * buf, std::shared_ptr<const DNNPMessage> 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 DNNPMessage> msg, UnconfirmedTags * newTags)
{
uint64_t ts = dotnet::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 DNNPMessage> msg, bool isDestination)
{
uint64_t ts = dotnet::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 = dotnet::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 = dotnet::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<DNNPMessage> msg)
{
uint8_t * buf = msg->GetPayload ();
uint32_t length = bufbe32toh (buf);
if (length > msg->GetLength ())
{
LogPrint (eLogWarning, "Garlic: message length ", length, " exceeds DNNP 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 && Decrypt (buf, (uint8_t *)&elGamal, m_Ctx))
{
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<dotnet::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 = dotnet::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<dotnet::tunnel::InboundTunnel> from)
{
if (len < 1)
{
LogPrint (eLogError, "Garlic: payload is too short");
return;
}
int numCloves = buf[0];
LogPrint (eLogDebug, "Garlic: ", numCloves," cloves");
buf++; len--;
for (int i = 0; i < numCloves; i++)
{
const uint8_t * buf1 = buf;
// delivery instructions
uint8_t flag = buf[0];
buf++; // flag
if (flag & 0x80) // encrypted?
{
// TODO: implement
LogPrint (eLogWarning, "Garlic: clove encrypted");
buf += 32;
}
ptrdiff_t offset = buf - buf1;
GarlicDeliveryType deliveryType = (GarlicDeliveryType)((flag >> 5) & 0x03);
switch (deliveryType)
{
case eGarlicDeliveryTypeLocal:
LogPrint (eLogDebug, "Garlic: type local");
if (offset > (int)len)
{
LogPrint (eLogError, "Garlic: message is too short");
break;
}
HandleDNNPMessage (buf, len - offset, from);
break;
case eGarlicDeliveryTypeDestination:
LogPrint (eLogDebug, "Garlic: type destination");
buf += 32; // destination. check it later or for multiple destinations
offset = buf - buf1;
if (offset > (int)len)
{
LogPrint (eLogError, "Garlic: message is too short");
break;
}
HandleDNNPMessage (buf, len - offset, from);
break;
case eGarlicDeliveryTypeTunnel:
{
LogPrint (eLogDebug, "Garlic: type tunnel");
// gwHash and gwTunnel sequence is reverted
uint8_t * gwHash = buf;
buf += 32;
offset = buf - buf1;
if (offset + 4 > (int)len)
{
LogPrint (eLogError, "Garlic: message is too short");
break;
}
uint32_t gwTunnel = bufbe32toh (buf);
buf += 4; offset += 4;
auto msg = CreateDNNPMessage (buf, GetDNNPMessageLength (buf, len - offset), from);
if (from) // received through an inbound tunnel
{
std::shared_ptr<dotnet::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 sent it through an outbound tunnel
tunnel->SendTunnelDataMsg (gwHash, gwTunnel, msg);
else
LogPrint (eLogWarning, "Garlic: No outbound tunnels available for garlic clove");
}
else // received directly
dotnet::transport::transports.SendMessage (gwHash, dotnet::CreateTunnelGatewayMsg (gwTunnel, msg)); // send directly
break;
}
case eGarlicDeliveryTypeRouter:
{
uint8_t * ident = buf;
buf += 32;
offset = buf - buf1;
if (!from) // received directly
{
if (offset > (int)len)
{
LogPrint (eLogError, "Garlic: message is too short");
break;
}
dotnet::transport::transports.SendMessage (ident,
CreateDNNPMessage (buf, GetDNNPMessageLength (buf, len - offset)));
}
else
LogPrint (eLogWarning, "Garlic: type router for inbound tunnels not supported");
break;
}
default:
LogPrint (eLogWarning, "Garlic: unknown delivery type ", (int)deliveryType);
}
if (offset > (int)len)
{
LogPrint (eLogError, "Garlic: message is too short");
break;
}
buf += GetDNNPMessageLength (buf, len - offset); // DNNP
buf += 4; // CloveID
buf += 8; // Date
buf += 3; // Certificate
offset = buf - buf1;
if (offset > (int)len)
{
LogPrint (eLogError, "Garlic: clove is too long");
break;
}
len -= offset;
}
}
std::shared_ptr<DNNPMessage> GarlicDestination::WrapMessage (std::shared_ptr<const dotnet::data::RoutingDestination> destination,
std::shared_ptr<DNNPMessage> msg, bool attachLeaseSet)
{
auto session = GetRoutingSession (destination, attachLeaseSet);
return session->WrapSingleMessage (msg);
}
std::shared_ptr<GarlicRoutingSession> GarlicDestination::GetRoutingSession (
std::shared_ptr<const dotnet::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 = dotnet::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<DNNPMessage> 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<DNNPMessage> msg)
{
HandleGarlicMessage (msg);
}
void GarlicDestination::ProcessDeliveryStatusMessage (std::shared_ptr<DNNPMessage> msg)
{
HandleDeliveryStatusMessage (msg);
}
void GarlicDestination::SaveTags ()
{
if (m_Tags.empty ()) return;
std::string ident = GetIdentHash().ToBase32();
std::string path = dotnet::fs::DataDirPath("tags", (ident + ".tags"));
std::ofstream f (path, std::ofstream::binary | std::ofstream::out | std::ofstream::trunc);
uint32_t ts = dotnet::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 = dotnet::fs::DataDirPath("tags", (ident + ".tags"));
uint32_t ts = dotnet::util::GetSecondsSinceEpoch ();
if (ts < dotnet::fs::GetLastUpdateTime (path) + INCOMING_TAGS_EXPIRATION_TIMEOUT)
{
// might contain non-expired tags
std::ifstream f (path, std::ifstream::binary);
if (f)
{
std::map<dotnet::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);
}
}
dotnet::fs::Remove (path);
}
void CleanUpTagsFiles ()
{
std::vector<std::string> files;
dotnet::fs::ReadDir (dotnet::fs::DataDirPath("tags"), files);
uint32_t ts = dotnet::util::GetSecondsSinceEpoch ();
for (auto it: files)
if (ts >= dotnet::fs::GetLastUpdateTime (it) + INCOMING_TAGS_EXPIRATION_TIMEOUT)
dotnet::fs::Remove (it);
}
}
}

234
libdotnet/Garlic.h Normal file
View 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 "DNNPProtocol.h"
#include "LeaseSet.h"
#include "Queue.h"
#include "Identity.h"
namespace dotnet
{
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 dotnet::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 dotnet::crypto::CBCDecryption
{
public:
AESDecryption (const uint8_t * key): m_Key (key)
{
SetKey (key);
}
const dotnet::crypto::AESKey& GetKey () const { return m_Key; };
private:
dotnet::crypto::AESKey m_Key;
};
struct GarlicRoutingPath
{
std::shared_ptr<dotnet::tunnel::OutboundTunnel> outboundTunnel;
std::shared_ptr<const dotnet::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 dotnet::data::RoutingDestination> destination,
int numTags, bool attachLeaseSet);
GarlicRoutingSession (const uint8_t * sessionKey, const SessionTag& sessionTag); // one time encryption
~GarlicRoutingSession ();
std::shared_ptr<DNNPMessage> WrapSingleMessage (std::shared_ptr<const DNNPMessage> 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 DNNPMessage> msg);
size_t CreateGarlicPayload (uint8_t * payload, std::shared_ptr<const DNNPMessage> msg, UnconfirmedTags * newTags);
size_t CreateGarlicClove (uint8_t * buf, std::shared_ptr<const DNNPMessage> 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 dotnet::data::RoutingDestination> m_Destination;
dotnet::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
dotnet::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 dotnet::data::LocalDestination
{
public:
GarlicDestination ();
~GarlicDestination ();
void CleanUp ();
void SetNumTags (int numTags) { m_NumTags = numTags; };
std::shared_ptr<GarlicRoutingSession> GetRoutingSession (std::shared_ptr<const dotnet::data::RoutingDestination> destination, bool attachLeaseSet);
void CleanupExpiredTags ();
void RemoveDeliveryStatusSession (uint32_t msgID);
std::shared_ptr<DNNPMessage> WrapMessage (std::shared_ptr<const dotnet::data::RoutingDestination> destination,
std::shared_ptr<DNNPMessage> 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<DNNPMessage> msg);
virtual void ProcessDeliveryStatusMessage (std::shared_ptr<DNNPMessage> msg);
virtual void SetLeaseSetUpdated ();
virtual std::shared_ptr<const dotnet::data::LocalLeaseSet> GetLeaseSet () = 0; // TODO
virtual std::shared_ptr<dotnet::tunnel::TunnelPool> GetTunnelPool () const = 0;
virtual void HandleDNNPMessage (const uint8_t * buf, size_t len, std::shared_ptr<dotnet::tunnel::InboundTunnel> from) = 0;
protected:
void HandleGarlicMessage (std::shared_ptr<DNNPMessage> msg);
void HandleDeliveryStatusMessage (std::shared_ptr<DNNPMessage> msg);
void SaveTags ();
void LoadTags ();
private:
void HandleAESBlock (uint8_t * buf, size_t len, std::shared_ptr<AESDecryption> decryption,
std::shared_ptr<dotnet::tunnel::InboundTunnel> from);
void HandleGarlicPayload (uint8_t * buf, size_t len, std::shared_ptr<dotnet::tunnel::InboundTunnel> from);
private:
BN_CTX * m_Ctx; // incoming
// outgoing sessions
int m_NumTags;
std::mutex m_SessionsMutex;
std::map<dotnet::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

1003
libdotnet/Gost.cpp Normal file

File diff suppressed because it is too large Load diff

62
libdotnet/Gost.h Normal file
View file

@ -0,0 +1,62 @@
#ifndef GOST_H__
#define GOST_H__
#include <memory>
#include <openssl/ec.h>
namespace dotnet
{
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
libdotnet/Gzip.cpp Normal file
View file

@ -0,0 +1,115 @@
/*
* Copyright (c) 2013-2017, The PurpleI2P Project
*
* This file is part of Purple dotnet 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 dotnet
{
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
} // dotnet

44
libdotnet/Gzip.h Normal file
View file

@ -0,0 +1,44 @@
#ifndef GZIP_H__
#define GZIP_H__
#include <zlib.h>
namespace dotnet {
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
} // dotnet
#endif /* GZIP_H__ */

503
libdotnet/HTTP.cpp Normal file
View file

@ -0,0 +1,503 @@
/*
* Copyright (c) 2013-2017, The PurpleI2P Project
*
* This file is part of Purple dotnet project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*/
#include <algorithm>
#include <utility>
#include <stdio.h>
#include "util.h"
#include "HTTP.h"
#include <ctime>
namespace dotnet {
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"
};
const std::vector<const char *> weekdays = {
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
};
const std::vector<const char *> months = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
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 = 1; /*: */
std::size_t max = line.length();
if ((pos = line.find(':', pos)) == std::string::npos)
return std::make_pair("", ""); // no ':' found
if (pos + 1 < max) // ':' at the end of header is valid
{
while ((pos + len) < max && isspace(line.at(pos + len)))
len++;
if (len == 1) return std::make_pair("", ""); // no following space, but something else
}
return std::make_pair(line.substr(0, pos), line.substr(pos + len));
}
void gen_rfc7231_date(std::string & out) {
std::time_t now = std::time(nullptr);
char buf[128];
std::tm *tm = std::gmtime(&now);
snprintf(buf, sizeof(buf), "%s, %02d %s %d %02d:%02d:%02d GMT",
weekdays[tm->tm_wday], tm->tm_mday, months[tm->tm_mon],
tm->tm_year + 1900, tm->tm_hour, tm->tm_min, tm->tm_sec
);
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 && 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 if(delim) {
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;
}
bool URL::is_dotnet() const
{
return host.rfind(".dotnet") == ( host.size() - 4 );
}
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, const std::string& exempt)
{
for (auto it = headers.begin (); it != headers.end ();)
{
if (!it->first.compare(0, name.length (), name) && it->first != exempt)
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 includingDOTNETGzip) 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 (includingDOTNETGzip && it->second.find("x-dotnet-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_rfc7231_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
} // dotnet

172
libdotnet/HTTP.h Normal file
View file

@ -0,0 +1,172 @@
/*
* Copyright (c) 2013-2016, The PurpleI2P Project
*
* This file is part of Purple dotnet 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 dotnet
{
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 ();
/**
* @brief return true if the host is inside dotnet
*/
bool is_dotnet() const;
};
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, const std::string& exempt); // remove all headers starting with name, but exempt
void RemoveHeader (const std::string& name) { RemoveHeader (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 includingDOTNETGzip = 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
} // dotnet
#endif /* HTTP_H__ */

829
libdotnet/Identity.cpp Normal file
View file

@ -0,0 +1,829 @@
#include "Crypto.h"
#include "DotNetEndian.h"
#include "Log.h"
#include "Timestamp.h"
#include "Identity.h"
namespace dotnet
{
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, CryptoKeyType cryptoType):
m_IsVerifierCreated (false)
{
memcpy (m_StandardIdentity.publicKey, publicKey, 256); // publicKey in awlays assumed 256 regardless actual size, padding must be taken care of
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 - dotnet::crypto::ECDSAP256_KEY_LENGTH; // 64 = 128 - 64
RAND_bytes (m_StandardIdentity.signingKey, padding);
memcpy (m_StandardIdentity.signingKey + padding, signingKey, dotnet::crypto::ECDSAP256_KEY_LENGTH);
break;
}
case SIGNING_KEY_TYPE_ECDSA_SHA384_P384:
{
size_t padding = 128 - dotnet::crypto::ECDSAP384_KEY_LENGTH; // 32 = 128 - 96
RAND_bytes (m_StandardIdentity.signingKey, padding);
memcpy (m_StandardIdentity.signingKey + padding, signingKey, dotnet::crypto::ECDSAP384_KEY_LENGTH);
break;
}
case SIGNING_KEY_TYPE_ECDSA_SHA512_P521:
{
memcpy (m_StandardIdentity.signingKey, signingKey, 128);
excessLen = dotnet::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:
case SIGNING_KEY_TYPE_RSA_SHA384_3072:
case SIGNING_KEY_TYPE_RSA_SHA512_4096:
LogPrint (eLogError, "Identity: RSA signing key type ", (int)type, " is not supported");
break;
case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519:
case SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519:
{
size_t padding = 128 - dotnet::crypto::EDDSA25519_PUBLIC_KEY_LENGTH; // 96 = 128 - 32
RAND_bytes (m_StandardIdentity.signingKey, padding);
memcpy (m_StandardIdentity.signingKey + padding, signingKey, dotnet::crypto::EDDSA25519_PUBLIC_KEY_LENGTH);
break;
}
case SIGNING_KEY_TYPE_GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256:
{
// 256
size_t padding = 128 - dotnet::crypto::GOSTR3410_256_PUBLIC_KEY_LENGTH; // 64 = 128 - 64
RAND_bytes (m_StandardIdentity.signingKey, padding);
memcpy (m_StandardIdentity.signingKey + padding, signingKey, dotnet::crypto::GOSTR3410_256_PUBLIC_KEY_LENGTH);
break;
}
case SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512:
{
// 512
// no padding, key length is 128
memcpy (m_StandardIdentity.signingKey, signingKey, dotnet::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, cryptoType);
if (excessLen && excessBuf)
{
memcpy (m_ExtendedBuffer + 4, excessBuf, excessLen);
delete[] excessBuf;
}
// calculate ident hash
RecalculateIdentHash();
}
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 ();
}
void IdentityEx::RecalculateIdentHash(uint8_t * buf)
{
bool dofree = buf == nullptr;
size_t sz = GetFullLen();
if(!buf)
buf = new uint8_t[sz];
ToBuffer (buf, sz);
SHA256(buf, sz, m_IdentHash);
if(dofree)
delete[] buf;
}
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 = dotnet::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;
}
const uint8_t * IdentityEx::GetSigningPublicKeyBuffer () const
{
auto keyLen = GetSigningPublicKeyLen ();
if (keyLen > 128) return nullptr; // P521
return m_StandardIdentity.signingKey + 128 - keyLen;
}
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 dotnet::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;
}
bool IdentityEx::IsRSA () const
{
auto sigType = GetSigningKeyType ();
return sigType <= SIGNING_KEY_TYPE_RSA_SHA512_4096 && sigType >= SIGNING_KEY_TYPE_RSA_SHA256_2048;
}
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;
}
dotnet::crypto::Verifier * IdentityEx::CreateVerifier (SigningKeyType keyType)
{
switch (keyType)
{
case SIGNING_KEY_TYPE_DSA_SHA1:
return new dotnet::crypto::DSAVerifier ();
case SIGNING_KEY_TYPE_ECDSA_SHA256_P256:
return new dotnet::crypto::ECDSAP256Verifier ();
case SIGNING_KEY_TYPE_ECDSA_SHA384_P384:
return new dotnet::crypto::ECDSAP384Verifier ();
case SIGNING_KEY_TYPE_ECDSA_SHA512_P521:
return new dotnet::crypto::ECDSAP521Verifier ();
case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519:
return new dotnet::crypto::EDDSA25519Verifier ();
case SIGNING_KEY_TYPE_GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256:
return new dotnet::crypto::GOSTR3410_256_Verifier (dotnet::crypto::eGOSTR3410CryptoProA);
case SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512:
return new dotnet::crypto::GOSTR3410_512_Verifier (dotnet::crypto::eGOSTR3410TC26A512);
case SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519:
return new dotnet::crypto::RedDSA25519Verifier ();
case SIGNING_KEY_TYPE_RSA_SHA256_2048:
case SIGNING_KEY_TYPE_RSA_SHA384_3072:
case SIGNING_KEY_TYPE_RSA_SHA512_4096:
LogPrint (eLogError, "Identity: RSA signing key type ", (int)keyType, " is not supported");
break;
default:
LogPrint (eLogError, "Identity: Signing key type ", (int)keyType, " is not supported");
}
return nullptr;
}
void IdentityEx::CreateVerifier () const
{
if (m_Verifier) return; // don't create again
auto verifier = CreateVerifier (GetSigningKeyType ());
if (verifier)
{
auto keyLen = verifier->GetPublicKeyLen ();
if (keyLen <= 128)
verifier->SetPublicKey (m_StandardIdentity.signingKey + 128 - keyLen);
else
{
// for P521
uint8_t * signingKey = new uint8_t[keyLen];
memcpy (signingKey, m_StandardIdentity.signingKey, 128);
size_t excessLen = keyLen - 128;
memcpy (signingKey + 128, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types
verifier->SetPublicKey (signingKey);
delete[] signingKey;
}
}
UpdateVerifier (verifier);
}
void IdentityEx::UpdateVerifier (dotnet::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;
}
std::shared_ptr<dotnet::crypto::CryptoKeyEncryptor> IdentityEx::CreateEncryptor (CryptoKeyType keyType, const uint8_t * key)
{
switch (keyType)
{
case CRYPTO_KEY_TYPE_ELGAMAL:
return std::make_shared<dotnet::crypto::ElGamalEncryptor>(key);
break;
case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC:
case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC_TEST:
return std::make_shared<dotnet::crypto::ECIESP256Encryptor>(key);
break;
case CRYPTO_KEY_TYPE_ECIES_GOSTR3410_CRYPTO_PRO_A_SHA256_AES256CBC:
return std::make_shared<dotnet::crypto::ECIESGOSTR3410Encryptor>(key);
break;
default:
LogPrint (eLogError, "Identity: Unknown crypto key type ", (int)keyType);
};
return nullptr;
}
std::shared_ptr<dotnet::crypto::CryptoKeyEncryptor> IdentityEx::CreateEncryptor (const uint8_t * key) const
{
if (!key) key = GetEncryptionPublicKey (); // use publicKey
return CreateEncryptor (GetCryptoKeyType (), key);
}
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_OfflineSignature.resize (0);
m_TransientSignatureLen = 0;
m_TransientSigningPrivateKeyLen = 0;
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
m_OfflineSignature = other.m_OfflineSignature;
m_TransientSignatureLen = other.m_TransientSignatureLen;
m_TransientSigningPrivateKeyLen = other.m_TransientSigningPrivateKeyLen;
memcpy (m_SigningPrivateKey, other.m_SigningPrivateKey, m_TransientSigningPrivateKeyLen > 0 ? m_TransientSigningPrivateKeyLen : m_Public->GetSigningPrivateKeyLen ());
m_Signer = nullptr;
CreateSigner ();
return *this;
}
size_t PrivateKeys::GetFullLen () const
{
size_t ret = m_Public->GetFullLen () + 256 + m_Public->GetSigningPrivateKeyLen ();
if (IsOfflineSignature ())
ret += m_OfflineSignature.size () + m_TransientSigningPrivateKeyLen;
return ret;
}
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 || signingPrivateKeySize > 128) return 0; // overflow
memcpy (m_SigningPrivateKey, buf + ret, signingPrivateKeySize);
ret += signingPrivateKeySize;
m_Signer = nullptr;
// check if signing private key is all zeros
bool allzeros = true;
for (size_t i = 0; i < signingPrivateKeySize; i++)
if (m_SigningPrivateKey[i])
{
allzeros = false;
break;
}
if (allzeros)
{
// offline information
const uint8_t * offlineInfo = buf + ret;
ret += 4; // expires timestamp
SigningKeyType keyType = bufbe16toh (buf + ret); ret += 2; // key type
std::unique_ptr<dotnet::crypto::Verifier> transientVerifier (IdentityEx::CreateVerifier (keyType));
if (!transientVerifier) return 0;
auto keyLen = transientVerifier->GetPublicKeyLen ();
if (keyLen + ret > len) return 0;
transientVerifier->SetPublicKey (buf + ret); ret += keyLen;
if (m_Public->GetSignatureLen () + ret > len) return 0;
if (!m_Public->Verify (offlineInfo, keyLen + 6, buf + ret))
{
LogPrint (eLogError, "Identity: offline signature verification failed");
return 0;
}
ret += m_Public->GetSignatureLen ();
m_TransientSignatureLen = transientVerifier->GetSignatureLen ();
// copy offline signature
size_t offlineInfoLen = buf + ret - offlineInfo;
m_OfflineSignature.resize (offlineInfoLen);
memcpy (m_OfflineSignature.data (), offlineInfo, offlineInfoLen);
// override signing private key
m_TransientSigningPrivateKeyLen = transientVerifier->GetPrivateKeyLen ();
if (m_TransientSigningPrivateKeyLen + ret > len || m_TransientSigningPrivateKeyLen > 128) return 0;
memcpy (m_SigningPrivateKey, buf + ret, m_TransientSigningPrivateKeyLen);
ret += m_TransientSigningPrivateKeyLen;
CreateSigner (keyType);
}
else
CreateSigner (m_Public->GetSigningKeyType ());
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
if (IsOfflineSignature ())
memset (buf + ret, 0, signingPrivateKeySize);
else
memcpy (buf + ret, m_SigningPrivateKey, signingPrivateKeySize);
ret += signingPrivateKeySize;
if (IsOfflineSignature ())
{
// offline signature
auto offlineSignatureLen = m_OfflineSignature.size ();
if (ret + offlineSignatureLen > len) return 0;
memcpy (buf + ret, m_OfflineSignature.data (), offlineSignatureLen);
ret += offlineSignatureLen;
// transient private key
if (ret + m_TransientSigningPrivateKeyLen > len) return 0;
memcpy (buf + ret, m_SigningPrivateKey, m_TransientSigningPrivateKeyLen);
ret += m_TransientSigningPrivateKeyLen;
}
return ret;
}
size_t PrivateKeys::FromBase64(const std::string& s)
{
uint8_t * buf = new uint8_t[s.length ()];
size_t l = dotnet::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 = dotnet::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 (IsOfflineSignature ())
CreateSigner (bufbe16toh (m_OfflineSignature.data () + 4)); // key type
else
CreateSigner (m_Public->GetSigningKeyType ());
}
void PrivateKeys::CreateSigner (SigningKeyType keyType) const
{
if (m_Signer) return;
if (keyType == SIGNING_KEY_TYPE_DSA_SHA1)
m_Signer.reset (new dotnet::crypto::DSASigner (m_SigningPrivateKey, m_Public->GetStandardIdentity ().signingKey));
else if (keyType == SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519 && !IsOfflineSignature ())
m_Signer.reset (new dotnet::crypto::EDDSA25519Signer (m_SigningPrivateKey, m_Public->GetStandardIdentity ().certificate - dotnet::crypto::EDDSA25519_PUBLIC_KEY_LENGTH)); // TODO: remove public key check
else
{
// public key is not required
auto signer = CreateSigner (keyType, m_SigningPrivateKey);
if (signer) m_Signer.reset (signer);
}
}
dotnet::crypto::Signer * PrivateKeys::CreateSigner (SigningKeyType keyType, const uint8_t * priv)
{
switch (keyType)
{
case SIGNING_KEY_TYPE_ECDSA_SHA256_P256:
return new dotnet::crypto::ECDSAP256Signer (priv);
break;
case SIGNING_KEY_TYPE_ECDSA_SHA384_P384:
return new dotnet::crypto::ECDSAP384Signer (priv);
break;
case SIGNING_KEY_TYPE_ECDSA_SHA512_P521:
return new dotnet::crypto::ECDSAP521Signer (priv);
break;
case SIGNING_KEY_TYPE_RSA_SHA256_2048:
case SIGNING_KEY_TYPE_RSA_SHA384_3072:
case SIGNING_KEY_TYPE_RSA_SHA512_4096:
LogPrint (eLogError, "Identity: RSA signing key type ", (int)keyType, " is not supported");
break;
case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519:
return new dotnet::crypto::EDDSA25519Signer (priv, nullptr);
break;
case SIGNING_KEY_TYPE_GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256:
return new dotnet::crypto::GOSTR3410_256_Signer (dotnet::crypto::eGOSTR3410CryptoProA, priv);
break;
case SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512:
return new dotnet::crypto::GOSTR3410_512_Signer (dotnet::crypto::eGOSTR3410TC26A512, priv);
break;
case SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519:
return new dotnet::crypto::RedDSA25519Signer (priv);
break;
default:
LogPrint (eLogError, "Identity: Signing key type ", (int)keyType, " is not supported");
}
return nullptr;
}
size_t PrivateKeys::GetSignatureLen () const
{
return IsOfflineSignature () ? m_TransientSignatureLen : m_Public->GetSignatureLen ();
}
uint8_t * PrivateKeys::GetPadding()
{
if(m_Public->GetSigningKeyType () == SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519)
return m_Public->GetEncryptionPublicKeyBuffer() + 256;
else
return nullptr; // TODO: implement me
}
std::shared_ptr<dotnet::crypto::CryptoKeyDecryptor> PrivateKeys::CreateDecryptor (const uint8_t * key) const
{
if (!key) key = m_PrivateKey; // use privateKey
return CreateDecryptor (m_Public->GetCryptoKeyType (), key);
}
std::shared_ptr<dotnet::crypto::CryptoKeyDecryptor> PrivateKeys::CreateDecryptor (CryptoKeyType cryptoType, const uint8_t * key)
{
if (!key) return nullptr;
switch (cryptoType)
{
case CRYPTO_KEY_TYPE_ELGAMAL:
return std::make_shared<dotnet::crypto::ElGamalDecryptor>(key);
break;
case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC:
case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC_TEST:
return std::make_shared<dotnet::crypto::ECIESP256Decryptor>(key);
break;
case CRYPTO_KEY_TYPE_ECIES_GOSTR3410_CRYPTO_PRO_A_SHA256_AES256CBC:
return std::make_shared<dotnet::crypto::ECIESGOSTR3410Decryptor>(key);
break;
default:
LogPrint (eLogError, "Identity: Unknown crypto key type ", (int)cryptoType);
};
return nullptr;
}
PrivateKeys PrivateKeys::CreateRandomKeys (SigningKeyType type, CryptoKeyType cryptoType)
{
if (type != SIGNING_KEY_TYPE_DSA_SHA1)
{
PrivateKeys keys;
// signature
uint8_t signingPublicKey[512]; // signing public key is 512 bytes max
GenerateSigningKeyPair (type, keys.m_SigningPrivateKey, signingPublicKey);
// encryption
uint8_t publicKey[256];
GenerateCryptoKeyPair (cryptoType, keys.m_PrivateKey, publicKey);
// identity
keys.m_Public = std::make_shared<IdentityEx> (publicKey, signingPublicKey, type, cryptoType);
keys.CreateSigner ();
return keys;
}
return PrivateKeys (dotnet::data::CreateRandomKeys ()); // DSA-SHA1
}
void PrivateKeys::GenerateSigningKeyPair (SigningKeyType type, uint8_t * priv, uint8_t * pub)
{
switch (type)
{
case SIGNING_KEY_TYPE_ECDSA_SHA256_P256:
dotnet::crypto::CreateECDSAP256RandomKeys (priv, pub);
break;
case SIGNING_KEY_TYPE_ECDSA_SHA384_P384:
dotnet::crypto::CreateECDSAP384RandomKeys (priv, pub);
break;
case SIGNING_KEY_TYPE_ECDSA_SHA512_P521:
dotnet::crypto::CreateECDSAP521RandomKeys (priv, pub);
break;
case SIGNING_KEY_TYPE_RSA_SHA256_2048:
case SIGNING_KEY_TYPE_RSA_SHA384_3072:
case SIGNING_KEY_TYPE_RSA_SHA512_4096:
LogPrint (eLogWarning, "Identity: RSA signature type is not supported. Creating EdDSA");
// no break here
case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519:
dotnet::crypto::CreateEDDSA25519RandomKeys (priv, pub);
break;
case SIGNING_KEY_TYPE_GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256:
dotnet::crypto::CreateGOSTR3410RandomKeys (dotnet::crypto::eGOSTR3410CryptoProA, priv, pub);
break;
case SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512:
dotnet::crypto::CreateGOSTR3410RandomKeys (dotnet::crypto::eGOSTR3410TC26A512, priv, pub);
break;
case SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519:
dotnet::crypto::CreateRedDSA25519RandomKeys (priv, pub);
break;
default:
LogPrint (eLogWarning, "Identity: Signing key type ", (int)type, " is not supported. Create DSA-SHA1");
dotnet::crypto::CreateDSARandomKeys (priv, pub); // DSA-SHA1
}
}
void PrivateKeys::GenerateCryptoKeyPair (CryptoKeyType type, uint8_t * priv, uint8_t * pub)
{
switch (type)
{
case CRYPTO_KEY_TYPE_ELGAMAL:
dotnet::crypto::GenerateElGamalKeyPair(priv, pub);
break;
case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC:
case CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC_TEST:
dotnet::crypto::CreateECIESP256RandomKeys (priv, pub);
break;
case CRYPTO_KEY_TYPE_ECIES_GOSTR3410_CRYPTO_PRO_A_SHA256_AES256CBC:
dotnet::crypto::CreateECIESGOSTR3410RandomKeys (priv, pub);
break;
default:
LogPrint (eLogError, "Identity: Crypto key type ", (int)type, " is not supported");
}
}
PrivateKeys PrivateKeys::CreateOfflineKeys (SigningKeyType type, uint32_t expires) const
{
PrivateKeys keys (*this);
std::unique_ptr<dotnet::crypto::Verifier> verifier (IdentityEx::CreateVerifier (type));
if (verifier)
{
size_t pubKeyLen = verifier->GetPublicKeyLen ();
keys.m_TransientSigningPrivateKeyLen = verifier->GetPrivateKeyLen ();
keys.m_TransientSignatureLen = verifier->GetSignatureLen ();
keys.m_OfflineSignature.resize (pubKeyLen + m_Public->GetSignatureLen () + 6);
htobe32buf (keys.m_OfflineSignature.data (), expires); // expires
htobe16buf (keys.m_OfflineSignature.data () + 4, type); // type
GenerateSigningKeyPair (type, keys.m_SigningPrivateKey, keys.m_OfflineSignature.data () + 6); // public key
Sign (keys.m_OfflineSignature.data (), pubKeyLen + 6, keys.m_OfflineSignature.data () + 6 + pubKeyLen); // signature
// recreate signer
keys.m_Signer = nullptr;
keys.CreateSigner (type);
}
return keys;
}
Keys CreateRandomKeys ()
{
Keys keys;
// encryption
dotnet::crypto::GenerateElGamalKeyPair(keys.privateKey, keys.publicKey);
// signing
dotnet::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);
dotnet::util::GetCurrentDate ((char *)(buf + 32));
IdentHash key;
SHA256(buf, 40, key);
return key;
}
XORMetric operator^(const IdentHash& key1, const IdentHash& key2)
{
XORMetric m;
#ifdef __AVX__
if(dotnet::cpu::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
#endif
{
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];
}
return m;
}
}
}

234
libdotnet/Identity.h Normal file
View file

@ -0,0 +1,234 @@
#ifndef IDENTITY_H__
#define IDENTITY_H__
#include <inttypes.h>
#include <string.h>
#include <string>
#include <memory>
#include <atomic>
#include <vector>
#include "Base.h"
#include "Signature.h"
#include "CryptoKey.h"
namespace dotnet
{
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 CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC = 1;
const uint16_t CRYPTO_KEY_TYPE_ECIES_P256_SHA256_AES256CBC_TEST = 65280; // TODO: remove later
const uint16_t CRYPTO_KEY_TYPE_ECIES_GOSTR3410_CRYPTO_PRO_A_SHA256_AES256CBC = 65281; // TODO: use GOST R 34.11 instead SHA256 and GOST 28147-89 instead AES
const uint16_t 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
const uint16_t SIGNING_KEY_TYPE_GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256 = 9;
const uint16_t SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512 = 10; // approved by FSB
const uint16_t SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519 = 11; // for LeaseSet2 only
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, CryptoKeyType cryptoType = CRYPTO_KEY_TYPE_ELGAMAL);
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; };
uint8_t * GetEncryptionPublicKeyBuffer () { return m_StandardIdentity.publicKey; };
std::shared_ptr<dotnet::crypto::CryptoKeyEncryptor> CreateEncryptor (const uint8_t * key) const;
size_t GetFullLen () const { return m_ExtendedLen + DEFAULT_IDENTITY_SIZE; };
size_t GetSigningPublicKeyLen () const;
const uint8_t * GetSigningPublicKeyBuffer () const; // returns NULL for P521
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;
bool IsRSA () const; // signing key type
CryptoKeyType GetCryptoKeyType () const;
void DropVerifier () const; // to save memory
bool operator == (const IdentityEx & other) const { return GetIdentHash() == other.GetIdentHash(); }
void RecalculateIdentHash(uint8_t * buff=nullptr);
static dotnet::crypto::Verifier * CreateVerifier (SigningKeyType keyType);
static std::shared_ptr<dotnet::crypto::CryptoKeyEncryptor> CreateEncryptor (CryptoKeyType keyType, const uint8_t * key);
private:
void CreateVerifier () const;
void UpdateVerifier (dotnet::crypto::Verifier * verifier) const;
private:
Identity m_StandardIdentity;
IdentHash m_IdentHash;
mutable std::unique_ptr<dotnet::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; };
size_t GetSignatureLen () const; // might not match identity
bool IsOfflineSignature () const { return m_TransientSignatureLen > 0; };
uint8_t * GetPadding();
void RecalculateIdentHash(uint8_t * buf=nullptr) { m_Public->RecalculateIdentHash(buf); }
void Sign (const uint8_t * buf, int len, uint8_t * signature) const;
size_t GetFullLen () const;
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;
std::shared_ptr<dotnet::crypto::CryptoKeyDecryptor> CreateDecryptor (const uint8_t * key) const;
static std::shared_ptr<dotnet::crypto::CryptoKeyDecryptor> CreateDecryptor (CryptoKeyType cryptoType, const uint8_t * key);
static PrivateKeys CreateRandomKeys (SigningKeyType type = SIGNING_KEY_TYPE_DSA_SHA1, CryptoKeyType cryptoType = CRYPTO_KEY_TYPE_ELGAMAL);
static void GenerateSigningKeyPair (SigningKeyType type, uint8_t * priv, uint8_t * pub);
static void GenerateCryptoKeyPair (CryptoKeyType type, uint8_t * priv, uint8_t * pub); // priv and pub are 256 bytes long
static dotnet::crypto::Signer * CreateSigner (SigningKeyType keyType, const uint8_t * priv);
// offline keys
PrivateKeys CreateOfflineKeys (SigningKeyType type, uint32_t expires) const;
const std::vector<uint8_t>& GetOfflineSignature () const { return m_OfflineSignature; };
private:
void CreateSigner () const;
void CreateSigner (SigningKeyType keyType) const;
private:
std::shared_ptr<IdentityEx> m_Public;
uint8_t m_PrivateKey[256];
uint8_t m_SigningPrivateKey[128]; // assume private key doesn't exceed 128 bytes
mutable std::unique_ptr<dotnet::crypto::Signer> m_Signer;
std::vector<uint8_t> m_OfflineSignature; // non zero length, if applicable
size_t m_TransientSignatureLen = 0;
size_t m_TransientSigningPrivateKeyLen = 0;
};
// 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 std::shared_ptr<const IdentityEx> GetIdentity () const = 0;
virtual void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const = 0; // encrypt data for
virtual bool IsDestination () const = 0; // for garlic
const IdentHash& GetIdentHash () const { return GetIdentity ()->GetIdentHash (); };
};
class LocalDestination
{
public:
virtual ~LocalDestination() {};
virtual bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) const = 0;
virtual std::shared_ptr<const IdentityEx> GetIdentity () const = 0;
const IdentHash& GetIdentHash () const { return GetIdentity ()->GetIdentHash (); };
};
}
}
#endif

933
libdotnet/LeaseSet.cpp Normal file
View file

@ -0,0 +1,933 @@
#include <string.h>
#include <openssl/sha.h>
#include <openssl/hmac.h>
#include <zlib.h> // for crc32
#include "DotNetEndian.h"
#include "Crypto.h"
#include "Ed25519.h"
#include "Log.h"
#include "Timestamp.h"
#include "NetDb.hpp"
#include "Tunnel.h"
#include "LeaseSet.h"
namespace dotnet
{
namespace data
{
LeaseSet::LeaseSet (bool storeLeases):
m_IsValid (false), m_StoreLeases (storeLeases), m_ExpirationTime (0), m_EncryptionKey (nullptr),
m_Buffer (nullptr), m_BufferLen (0)
{
}
LeaseSet::LeaseSet (const uint8_t * buf, size_t len, bool storeLeases):
m_IsValid (true), m_StoreLeases (storeLeases), m_ExpirationTime (0), m_EncryptionKey (nullptr)
{
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, bool verifySignature)
{
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, verifySignature);
}
void LeaseSet::PopulateLeases ()
{
m_StoreLeases = true;
ReadFromBuffer (false);
}
void LeaseSet::ReadFromBuffer (bool readIdentity, bool verifySignature)
{
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;
}
if (m_StoreLeases)
{
if (!m_EncryptionKey) m_EncryptionKey = new uint8_t[256];
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;
}
UpdateLeasesBegin ();
// process leases
m_ExpirationTime = 0;
auto ts = dotnet::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
UpdateLease (lease, ts);
}
if (!m_ExpirationTime)
{
LogPrint (eLogWarning, "LeaseSet: all leases are expired. Dropped");
m_IsValid = false;
return;
}
m_ExpirationTime += LEASE_ENDDATE_THRESHOLD;
UpdateLeasesEnd ();
// verify
if (verifySignature && !m_Identity->Verify (m_Buffer, leases - m_Buffer, leases))
{
LogPrint (eLogWarning, "LeaseSet: verification failed");
m_IsValid = false;
}
}
void LeaseSet::UpdateLeasesBegin ()
{
// reset existing leases
if (m_StoreLeases)
for (auto& it: m_Leases)
it->isUpdated = false;
else
m_Leases.clear ();
}
void LeaseSet::UpdateLeasesEnd ()
{
// 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;
}
}
}
void LeaseSet::UpdateLease (const Lease& lease, uint64_t ts)
{
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 ");
}
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 = dotnet::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 = dotnet::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 = dotnet::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 = dotnet::util::GetMillisecondsSinceEpoch ();
return ts > m_ExpirationTime;
}
void LeaseSet::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const
{
if (!m_EncryptionKey) return;
auto encryptor = m_Identity->CreateEncryptor (m_EncryptionKey);
if (encryptor)
encryptor->Encrypt (data, encrypted, ctx, true);
}
void LeaseSet::SetBuffer (const uint8_t * buf, size_t len)
{
if (m_Buffer) delete[] m_Buffer;
m_Buffer = new uint8_t[len];
m_BufferLen = len;
memcpy (m_Buffer, buf, len);
}
BlindedPublicKey::BlindedPublicKey (std::shared_ptr<const IdentityEx> identity, SigningKeyType blindedKeyType):
m_BlindedSigType (blindedKeyType)
{
if (!identity) return;
auto len = identity->GetSigningPublicKeyLen ();
m_PublicKey.resize (len);
memcpy (m_PublicKey.data (), identity->GetSigningPublicKeyBuffer (), len);
m_SigType = identity->GetSigningKeyType ();
}
BlindedPublicKey::BlindedPublicKey (const std::string& b33)
{
uint8_t addr[40]; // TODO: define length from b33
size_t l = dotnet::data::Base32ToByteStream (b33.c_str (), b33.length (), addr, 40);
uint32_t checksum = crc32 (0, addr + 3, l - 3);
// checksum is Little Endian
addr[0] ^= checksum; addr[1] ^= (checksum >> 8); addr[2] ^= (checksum >> 16);
uint8_t flag = addr[0];
size_t offset = 1;
if (flag & 0x01) // two bytes signatures
{
m_SigType = bufbe16toh (addr + offset); offset += 2;
m_BlindedSigType = bufbe16toh (addr + offset); offset += 2;
}
else // one byte sig
{
m_SigType = addr[offset]; offset++;
m_BlindedSigType = addr[offset]; offset++;
}
std::unique_ptr<dotnet::crypto::Verifier> blindedVerifier (dotnet::data::IdentityEx::CreateVerifier (m_SigType));
if (blindedVerifier)
{
auto len = blindedVerifier->GetPublicKeyLen ();
if (offset + len <= l)
{
m_PublicKey.resize (len);
memcpy (m_PublicKey.data (), addr + offset, len);
}
else
LogPrint (eLogError, "LeaseSet2: public key in b33 address is too short for signature type ", (int)m_SigType);
}
else
LogPrint (eLogError, "LeaseSet2: unknown signature type ", (int)m_SigType, " in b33");
}
std::string BlindedPublicKey::ToB33 () const
{
if (m_PublicKey.size () > 32) return ""; // assume 25519
uint8_t addr[35]; char str[60]; // TODO: define actual length
addr[0] = 0; // flags
addr[1] = m_SigType; // sig type
addr[2] = m_BlindedSigType; // blinded sig type
memcpy (addr + 3, m_PublicKey.data (), m_PublicKey.size ());
uint32_t checksum = crc32 (0, addr + 3, m_PublicKey.size ());
// checksum is Little Endian
addr[0] ^= checksum; addr[1] ^= (checksum >> 8); addr[2] ^= (checksum >> 16);
auto l = ByteStreamToBase32 (addr, m_PublicKey.size () + 3, str, 60);
return std::string (str, str + l);
}
void BlindedPublicKey::GetCredential (uint8_t * credential) const
{
// A = destination's signing public key
// stA = signature type of A, 2 bytes big endian
uint16_t stA = htobe16 (GetSigType ());
// stA1 = signature type of blinded A, 2 bytes big endian
uint16_t stA1 = htobe16 (GetBlindedSigType ());
// credential = H("credential", A || stA || stA1)
H ("credential", { {GetPublicKey (), GetPublicKeyLen ()}, {(const uint8_t *)&stA, 2}, {(const uint8_t *)&stA1, 2} }, credential);
}
void BlindedPublicKey::GetSubcredential (const uint8_t * blinded, size_t len, uint8_t * subcredential) const
{
uint8_t credential[32];
GetCredential (credential);
// subcredential = H("subcredential", credential || blindedPublicKey)
H ("subcredential", { {credential, 32}, {blinded, len} }, subcredential);
}
void BlindedPublicKey::GenerateAlpha (const char * date, uint8_t * seed) const
{
uint16_t stA = htobe16 (GetSigType ()), stA1 = htobe16 (GetBlindedSigType ());
uint8_t salt[32];
//seed = HKDF(H("DOTNETGenerateAlpha", keydata), datestring || secret, "dotnetblinding1", 64)
H ("DOTNETGenerateAlpha", { {GetPublicKey (), GetPublicKeyLen ()}, {(const uint8_t *)&stA, 2}, {(const uint8_t *)&stA1, 2} }, salt);
dotnet::crypto::HKDF (salt, (const uint8_t *)date, 8, "dotnetblinding1", seed);
}
void BlindedPublicKey::GetBlindedKey (const char * date, uint8_t * blindedKey) const
{
uint8_t seed[64];
GenerateAlpha (date, seed);
dotnet::crypto::GetEd25519 ()->BlindPublicKey (GetPublicKey (), seed, blindedKey);
}
void BlindedPublicKey::BlindPrivateKey (const uint8_t * priv, const char * date, uint8_t * blindedPriv, uint8_t * blindedPub) const
{
uint8_t seed[64];
GenerateAlpha (date, seed);
dotnet::crypto::GetEd25519 ()->BlindPrivateKey (priv, seed, blindedPriv, blindedPub);
}
void BlindedPublicKey::H (const std::string& p, const std::vector<std::pair<const uint8_t *, size_t> >& bufs, uint8_t * hash) const
{
SHA256_CTX ctx;
SHA256_Init (&ctx);
SHA256_Update (&ctx, p.c_str (), p.length ());
for (const auto& it: bufs)
SHA256_Update (&ctx, it.first, it.second);
SHA256_Final (hash, &ctx);
}
dotnet::data::IdentHash BlindedPublicKey::GetStoreHash (const char * date) const
{
dotnet::data::IdentHash hash;
if (m_BlindedSigType == dotnet::data::SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519 ||
m_BlindedSigType == SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519)
{
uint8_t blinded[32];
if (date)
GetBlindedKey (date, blinded);
else
{
char currentDate[9];
dotnet::util::GetCurrentDate (currentDate);
GetBlindedKey (currentDate, blinded);
}
auto stA1 = htobe16 (m_BlindedSigType);
SHA256_CTX ctx;
SHA256_Init (&ctx);
SHA256_Update (&ctx, (const uint8_t *)&stA1, 2);
SHA256_Update (&ctx, blinded, 32);
SHA256_Final ((uint8_t *)hash, &ctx);
}
else
LogPrint (eLogError, "LeaseSet2: blinded key type ", (int)m_BlindedSigType, " is not supported");
return hash;
}
LeaseSet2::LeaseSet2 (uint8_t storeType, const uint8_t * buf, size_t len, bool storeLeases):
LeaseSet (storeLeases), m_StoreType (storeType), m_OrigStoreType (storeType)
{
SetBuffer (buf, len);
if (storeType == NETDB_STORE_TYPE_ENCRYPTED_LEASESET2)
ReadFromBufferEncrypted (buf, len, nullptr);
else
ReadFromBuffer (buf, len);
}
LeaseSet2::LeaseSet2 (const uint8_t * buf, size_t len, std::shared_ptr<const BlindedPublicKey> key):
LeaseSet (true), m_StoreType (NETDB_STORE_TYPE_ENCRYPTED_LEASESET2), m_OrigStoreType (NETDB_STORE_TYPE_ENCRYPTED_LEASESET2)
{
ReadFromBufferEncrypted (buf, len, key);
}
void LeaseSet2::Update (const uint8_t * buf, size_t len, bool verifySignature)
{
SetBuffer (buf, len);
if (GetStoreType () != NETDB_STORE_TYPE_ENCRYPTED_LEASESET2)
ReadFromBuffer (buf, len, false, verifySignature);
// TODO: implement encrypted
}
void LeaseSet2::ReadFromBuffer (const uint8_t * buf, size_t len, bool readIdentity, bool verifySignature)
{
// standard LS2 header
std::shared_ptr<const IdentityEx> identity;
if (readIdentity)
{
identity = std::make_shared<IdentityEx>(buf, len);
SetIdentity (identity);
}
else
identity = GetIdentity ();
size_t offset = identity->GetFullLen ();
if (offset + 8 >= len) return;
m_PublishedTimestamp = bufbe32toh (buf + offset); offset += 4; // published timestamp (seconds)
uint16_t expires = bufbe16toh (buf + offset); offset += 2; // expires (seconds)
SetExpirationTime ((m_PublishedTimestamp + expires)*1000LL); // in milliseconds
uint16_t flags = bufbe16toh (buf + offset); offset += 2; // flags
if (flags & LEASESET2_FLAG_OFFLINE_KEYS)
{
// transient key
m_TransientVerifier = ProcessOfflineSignature (identity, buf, len, offset);
if (!m_TransientVerifier)
{
LogPrint (eLogError, "LeaseSet2: offline signature failed");
return;
}
}
// type specific part
size_t s = 0;
switch (m_StoreType)
{
case NETDB_STORE_TYPE_STANDARD_LEASESET2:
s = ReadStandardLS2TypeSpecificPart (buf + offset, len - offset);
break;
case NETDB_STORE_TYPE_META_LEASESET2:
s = ReadMetaLS2TypeSpecificPart (buf + offset, len - offset);
break;
default:
LogPrint (eLogWarning, "LeaseSet2: Unexpected store type ", (int)m_StoreType);
}
if (!s) return;
offset += s;
if (verifySignature || m_TransientVerifier)
{
// verify signature
bool verified = m_TransientVerifier ? VerifySignature (m_TransientVerifier, buf, len, offset) :
VerifySignature (identity, buf, len, offset);
SetIsValid (verified);
}
}
template<typename Verifier>
bool LeaseSet2::VerifySignature (Verifier& verifier, const uint8_t * buf, size_t len, size_t signatureOffset)
{
if (signatureOffset + verifier->GetSignatureLen () > len) return false;
// we assume buf inside DatabaseStore message, so buf[-1] is valid memory
// change it for signature verification, and restore back
uint8_t c = buf[-1];
const_cast<uint8_t *>(buf)[-1] = m_StoreType;
bool verified = verifier->Verify (buf - 1, signatureOffset + 1, buf + signatureOffset);
const_cast<uint8_t *>(buf)[-1] = c;
if (!verified)
LogPrint (eLogWarning, "LeaseSet2: verification failed");
return verified;
}
size_t LeaseSet2::ReadStandardLS2TypeSpecificPart (const uint8_t * buf, size_t len)
{
size_t offset = 0;
// properties
uint16_t propertiesLen = bufbe16toh (buf + offset); offset += 2;
offset += propertiesLen; // skip for now. TODO: implement properties
if (offset + 1 >= len) return 0;
// key sections
uint16_t currentKeyType = 0;
int numKeySections = buf[offset]; offset++;
for (int i = 0; i < numKeySections; i++)
{
uint16_t keyType = bufbe16toh (buf + offset); offset += 2; // encryption key type
if (offset + 2 >= len) return 0;
uint16_t encryptionKeyLen = bufbe16toh (buf + offset); offset += 2;
if (offset + encryptionKeyLen >= len) return 0;
if (IsStoreLeases ()) // create encryptor with leases only
{
// we pick first valid key, higher key type has higher priority 4-1-0
// if two keys with of the same type, pick first
auto encryptor = dotnet::data::IdentityEx::CreateEncryptor (keyType, buf + offset);
if (encryptor && (!m_Encryptor || keyType > currentKeyType))
{
m_Encryptor = encryptor; // TODO: atomic
currentKeyType = keyType;
}
}
offset += encryptionKeyLen;
}
// leases
if (offset + 1 >= len) return 0;
int numLeases = buf[offset]; offset++;
auto ts = dotnet::util::GetMillisecondsSinceEpoch ();
if (IsStoreLeases ())
{
UpdateLeasesBegin ();
for (int i = 0; i < numLeases; i++)
{
if (offset + LEASE2_SIZE > len) return 0;
Lease lease;
lease.tunnelGateway = buf + offset; offset += 32; // gateway
lease.tunnelID = bufbe32toh (buf + offset); offset += 4; // tunnel ID
lease.endDate = bufbe32toh (buf + offset)*1000LL; offset += 4; // end date
UpdateLease (lease, ts);
}
UpdateLeasesEnd ();
}
else
offset += numLeases*LEASE2_SIZE; // 40 bytes per lease
return offset;
}
size_t LeaseSet2::ReadMetaLS2TypeSpecificPart (const uint8_t * buf, size_t len)
{
size_t offset = 0;
// properties
uint16_t propertiesLen = bufbe16toh (buf + offset); offset += 2;
offset += propertiesLen; // skip for now. TODO: implement properties
// entries
if (offset + 1 >= len) return 0;
int numEntries = buf[offset]; offset++;
for (int i = 0; i < numEntries; i++)
{
if (offset + 40 >= len) return 0;
offset += 32; // hash
offset += 3; // flags
offset += 1; // cost
offset += 4; // expires
}
// revocations
if (offset + 1 >= len) return 0;
int numRevocations = buf[offset]; offset++;
for (int i = 0; i < numRevocations; i++)
{
if (offset + 32 > len) return 0;
offset += 32; // hash
}
return offset;
}
void LeaseSet2::ReadFromBufferEncrypted (const uint8_t * buf, size_t len, std::shared_ptr<const BlindedPublicKey> key)
{
size_t offset = 0;
// blinded key
if (len < 2) return;
const uint8_t * stA1 = buf + offset; // stA1 = blinded signature type, 2 bytes big endian
uint16_t blindedKeyType = bufbe16toh (stA1); offset += 2;
std::unique_ptr<dotnet::crypto::Verifier> blindedVerifier (dotnet::data::IdentityEx::CreateVerifier (blindedKeyType));
if (!blindedVerifier) return;
auto blindedKeyLen = blindedVerifier->GetPublicKeyLen ();
if (offset + blindedKeyLen >= len) return;
const uint8_t * blindedPublicKey = buf + offset;
blindedVerifier->SetPublicKey (blindedPublicKey); offset += blindedKeyLen;
// expiration
if (offset + 8 >= len) return;
const uint8_t * publishedTimestamp = buf + offset;
m_PublishedTimestamp = bufbe32toh (publishedTimestamp); offset += 4; // published timestamp (seconds)
uint16_t expires = bufbe16toh (buf + offset); offset += 2; // expires (seconds)
SetExpirationTime ((m_PublishedTimestamp + expires)*1000LL); // in milliseconds
uint16_t flags = bufbe16toh (buf + offset); offset += 2; // flags
if (flags & LEASESET2_FLAG_OFFLINE_KEYS)
{
// transient key
m_TransientVerifier = ProcessOfflineSignature (blindedVerifier, buf, len, offset);
if (!m_TransientVerifier)
{
LogPrint (eLogError, "LeaseSet2: offline signature failed");
return;
}
}
// outer ciphertext
if (offset + 2 > len) return;
uint16_t lenOuterCiphertext = bufbe16toh (buf + offset); offset += 2;
const uint8_t * outerCiphertext = buf + offset;
offset += lenOuterCiphertext;
// verify signature
bool verified = m_TransientVerifier ? VerifySignature (m_TransientVerifier, buf, len, offset) :
VerifySignature (blindedVerifier, buf, len, offset);
SetIsValid (verified);
// handle ciphertext
if (verified && key && lenOuterCiphertext >= 32)
{
SetIsValid (false); // we must verify it again in Layer 2
if (blindedKeyType == dotnet::data::SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519)
{
// verify blinding
char date[9];
dotnet::util::GetDateString (m_PublishedTimestamp, date);
uint8_t blinded[32];
key->GetBlindedKey (date, blinded);
if (memcmp (blindedPublicKey, blinded, 32))
{
LogPrint (eLogError, "LeaseSet2: blinded public key doesn't match");
return;
}
}
// outer key
// outerInput = subcredential || publishedTimestamp
uint8_t subcredential[36];
key->GetSubcredential (blindedPublicKey, blindedKeyLen, subcredential);
memcpy (subcredential + 32, publishedTimestamp, 4);
// outerSalt = outerCiphertext[0:32]
// keys = HKDF(outerSalt, outerInput, "ELS2_L1K", 44)
uint8_t keys[64]; // 44 bytes actual data
dotnet::crypto::HKDF (outerCiphertext, subcredential, 36, "ELS2_L1K", keys);
// decrypt Layer 1
// outerKey = keys[0:31]
// outerIV = keys[32:43]
size_t lenOuterPlaintext = lenOuterCiphertext - 32;
std::vector<uint8_t> outerPlainText (lenOuterPlaintext);
dotnet::crypto::ChaCha20 (outerCiphertext + 32, lenOuterPlaintext, keys, keys + 32, outerPlainText.data ());
// inner key
// innerInput = authCookie || subcredential || publishedTimestamp, TODO: non-empty authCookie
// innerSalt = innerCiphertext[0:32]
// keys = HKDF(innerSalt, innerInput, "ELS2_L2K", 44)
// skip 1 byte flags
dotnet::crypto::HKDF (outerPlainText.data () + 1, subcredential, 36, "ELS2_L2K", keys); // no authCookie
// decrypt Layer 2
// innerKey = keys[0:31]
// innerIV = keys[32:43]
size_t lenInnerPlaintext = lenOuterPlaintext - 32 - 1;
std::vector<uint8_t> innerPlainText (lenInnerPlaintext);
dotnet::crypto::ChaCha20 (outerPlainText.data () + 32 + 1, lenInnerPlaintext, keys, keys + 32, innerPlainText.data ());
if (innerPlainText[0] == NETDB_STORE_TYPE_STANDARD_LEASESET2 || innerPlainText[0] == NETDB_STORE_TYPE_META_LEASESET2)
{
// override store type and buffer
m_StoreType = innerPlainText[0];
SetBuffer (innerPlainText.data () + 1, lenInnerPlaintext - 1);
// parse and verify Layer 2
ReadFromBuffer (innerPlainText.data () + 1, lenInnerPlaintext - 1);
}
else
LogPrint (eLogError, "LeaseSet2: unexpected LeaseSet type ", (int)innerPlainText[0], " inside encrypted LeaseSet");
}
}
void LeaseSet2::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const
{
auto encryptor = m_Encryptor; // TODO: atomic
if (encryptor)
encryptor->Encrypt (data, encrypted, ctx, true);
}
uint64_t LeaseSet2::ExtractTimestamp (const uint8_t * buf, size_t len) const
{
if (len < 8) return 0;
if (m_StoreType == NETDB_STORE_TYPE_ENCRYPTED_LEASESET2)
{
// encrypted LS2
size_t offset = 0;
uint16_t blindedKeyType = bufbe16toh (buf + offset); offset += 2;
std::unique_ptr<dotnet::crypto::Verifier> blindedVerifier (dotnet::data::IdentityEx::CreateVerifier (blindedKeyType));
if (!blindedVerifier) return 0 ;
auto blindedKeyLen = blindedVerifier->GetPublicKeyLen ();
if (offset + blindedKeyLen + 6 >= len) return 0;
offset += blindedKeyLen;
uint32_t timestamp = bufbe32toh (buf + offset); offset += 4;
uint16_t expires = bufbe16toh (buf + offset); offset += 2;
return (timestamp + expires)* 1000LL;
}
else
{
auto identity = GetIdentity ();
if (!identity) return 0;
size_t offset = identity->GetFullLen ();
if (offset + 6 >= len) return 0;
uint32_t timestamp = bufbe32toh (buf + offset); offset += 4;
uint16_t expires = bufbe16toh (buf + offset); offset += 2;
return (timestamp + expires)* 1000LL;
}
}
LocalLeaseSet::LocalLeaseSet (std::shared_ptr<const IdentityEx> identity, const uint8_t * encryptionPublicKey, std::vector<std::shared_ptr<dotnet::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 = dotnet::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 () + dotnet::tunnel::TUNNEL_EXPIRATION_TIMEOUT - dotnet::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/dotnet::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)
{
if (buf)
{
m_BufferLen = len;
m_Buffer = new uint8_t[m_BufferLen];
memcpy (m_Buffer, buf, len);
}
else
{
m_Buffer = nullptr;
m_BufferLen = 0;
}
}
bool LocalLeaseSet::IsExpired () const
{
auto ts = dotnet::util::GetMillisecondsSinceEpoch ();
return ts > m_ExpirationTime;
}
bool LeaseSetBufferValidate(const uint8_t * ptr, size_t sz, uint64_t & expires)
{
IdentityEx ident(ptr, sz);
size_t size = ident.GetFullLen ();
if (size > sz)
{
LogPrint (eLogError, "LeaseSet: identity length ", size, " exceeds buffer size ", sz);
return false;
}
// encryption key
size += 256;
// signing key (unused)
size += ident.GetSigningPublicKeyLen ();
uint8_t numLeases = ptr[size];
++size;
if (!numLeases || numLeases > MAX_NUM_LEASES)
{
LogPrint (eLogError, "LeaseSet: incorrect number of leases", (int)numLeases);
return false;
}
const uint8_t * leases = ptr + size;
expires = 0;
/** find lease with the max expiration timestamp */
for (int i = 0; i < numLeases; i++)
{
leases += 36; // gateway + tunnel ID
uint64_t endDate = bufbe64toh (leases);
leases += 8; // end date
if(endDate > expires)
expires = endDate;
}
return ident.Verify(ptr, leases - ptr, leases);
}
LocalLeaseSet2::LocalLeaseSet2 (uint8_t storeType, const dotnet::data::PrivateKeys& keys,
uint16_t keyType, uint16_t keyLen, const uint8_t * encryptionPublicKey,
std::vector<std::shared_ptr<dotnet::tunnel::InboundTunnel> > tunnels):
LocalLeaseSet (keys.GetPublic (), nullptr, 0)
{
auto identity = keys.GetPublic ();
// assume standard LS2
int num = tunnels.size ();
if (num > MAX_NUM_LEASES) num = MAX_NUM_LEASES;
m_BufferLen = identity->GetFullLen () + 4/*published*/ + 2/*expires*/ + 2/*flag*/ + 2/*properties len*/ +
1/*num keys*/ + 2/*key type*/ + 2/*key len*/ + keyLen/*key*/ + 1/*num leases*/ + num*LEASE2_SIZE + keys.GetSignatureLen ();
uint16_t flags = 0;
if (keys.IsOfflineSignature ())
{
flags |= LEASESET2_FLAG_OFFLINE_KEYS;
m_BufferLen += keys.GetOfflineSignature ().size ();
}
m_Buffer = new uint8_t[m_BufferLen + 1];
m_Buffer[0] = storeType;
// LS2 header
auto offset = identity->ToBuffer (m_Buffer + 1, m_BufferLen) + 1;
auto timestamp = dotnet::util::GetSecondsSinceEpoch ();
htobe32buf (m_Buffer + offset, timestamp); offset += 4; // published timestamp (seconds)
uint8_t * expiresBuf = m_Buffer + offset; offset += 2; // expires, fill later
htobe16buf (m_Buffer + offset, flags); offset += 2; // flags
if (keys.IsOfflineSignature ())
{
// offline signature
const auto& offlineSignature = keys.GetOfflineSignature ();
memcpy (m_Buffer + offset, offlineSignature.data (), offlineSignature.size ());
offset += offlineSignature.size ();
}
htobe16buf (m_Buffer + offset, 0); offset += 2; // properties len
// keys
m_Buffer[offset] = 1; offset++; // 1 key
htobe16buf (m_Buffer + offset, keyType); offset += 2; // key type
htobe16buf (m_Buffer + offset, keyLen); offset += 2; // key len
memcpy (m_Buffer + offset, encryptionPublicKey, keyLen); offset += keyLen; // key
// leases
uint32_t expirationTime = 0; // in seconds
m_Buffer[offset] = num; offset++; // num leases
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
auto ts = tunnels[i]->GetCreationTime () + dotnet::tunnel::TUNNEL_EXPIRATION_TIMEOUT - dotnet::tunnel::TUNNEL_EXPIRATION_THRESHOLD; // in seconds, 1 minute before expiration
if (ts > expirationTime) expirationTime = ts;
htobe32buf (m_Buffer + offset, ts);
offset += 4; // end date
}
// update expiration
SetExpirationTime (expirationTime*1000LL);
auto expires = expirationTime - timestamp;
htobe16buf (expiresBuf, expires > 0 ? expires : 0);
// sign
keys.Sign (m_Buffer, offset, m_Buffer + offset); // LS + leading store type
}
LocalLeaseSet2::LocalLeaseSet2 (uint8_t storeType, std::shared_ptr<const IdentityEx> identity, const uint8_t * buf, size_t len):
LocalLeaseSet (identity, nullptr, 0)
{
m_BufferLen = len;
m_Buffer = new uint8_t[m_BufferLen + 1];
memcpy (m_Buffer + 1, buf, len);
m_Buffer[0] = storeType;
}
LocalEncryptedLeaseSet2::LocalEncryptedLeaseSet2 (std::shared_ptr<const LocalLeaseSet2> ls, const dotnet::data::PrivateKeys& keys, dotnet::data::SigningKeyType blindedKeyType):
LocalLeaseSet2 (ls->GetIdentity ()), m_InnerLeaseSet (ls)
{
size_t lenInnerPlaintext = ls->GetBufferLen () + 1, lenOuterPlaintext = lenInnerPlaintext + 32 + 1,
lenOuterCiphertext = lenOuterPlaintext + 32;
m_BufferLen = 2/*blinded sig type*/ + 32/*blinded pub key*/ + 4/*published*/ + 2/*expires*/ + 2/*flags*/ + 2/*lenOuterCiphertext*/ + lenOuterCiphertext + 64/*signature*/;
m_Buffer = new uint8_t[m_BufferLen + 1];
m_Buffer[0] = NETDB_STORE_TYPE_ENCRYPTED_LEASESET2;
BlindedPublicKey blindedKey (ls->GetIdentity ());
auto timestamp = dotnet::util::GetSecondsSinceEpoch ();
char date[9];
dotnet::util::GetDateString (timestamp, date);
uint8_t blindedPriv[32], blindedPub[32];
blindedKey.BlindPrivateKey (keys.GetSigningPrivateKey (), date, blindedPriv, blindedPub);
std::unique_ptr<dotnet::crypto::Signer> blindedSigner (dotnet::data::PrivateKeys::CreateSigner (blindedKeyType, blindedPriv));
auto offset = 1;
htobe16buf (m_Buffer + offset, blindedKeyType); offset += 2; // Blinded Public Key Sig Type
memcpy (m_Buffer + offset, blindedPub, 32); offset += 32; // Blinded Public Key
htobe32buf (m_Buffer + offset, timestamp); offset += 4; // published timestamp (seconds)
auto nextMidnight = (timestamp/86400LL + 1)*86400LL; // 86400 = 24*3600 seconds
auto expirationTime = ls->GetExpirationTime ()/1000LL;
if (expirationTime > nextMidnight) expirationTime = nextMidnight;
SetExpirationTime (expirationTime*1000LL);
htobe16buf (m_Buffer + offset, expirationTime > timestamp ? expirationTime - timestamp : 0); offset += 2; // expires
uint16_t flags = 0;
htobe16buf (m_Buffer + offset, flags); offset += 2; // flags
htobe16buf (m_Buffer + offset, lenOuterCiphertext); offset += 2; // lenOuterCiphertext
// outerChipherText
// Layer 1
uint8_t subcredential[36];
blindedKey.GetSubcredential (blindedPub, 32, subcredential);
htobe32buf (subcredential + 32, timestamp); // outerInput = subcredential || publishedTimestamp
// keys = HKDF(outerSalt, outerInput, "ELS2_L1K", 44)
uint8_t keys1[64]; // 44 bytes actual data
RAND_bytes (m_Buffer + offset, 32); // outerSalt = CSRNG(32)
dotnet::crypto::HKDF (m_Buffer + offset, subcredential, 36, "ELS2_L1K", keys1);
offset += 32; // outerSalt
uint8_t * outerPlainText = m_Buffer + offset;
m_Buffer[offset] = 0; offset++; // flag
// Layer 2
// keys = HKDF(outerSalt, outerInput, "ELS2_L2K", 44)
uint8_t keys2[64]; // 44 bytes actual data
RAND_bytes (m_Buffer + offset, 32); // innerSalt = CSRNG(32)
dotnet::crypto::HKDF (m_Buffer + offset, subcredential, 36, "ELS2_L2K", keys2);
offset += 32; // innerSalt
m_Buffer[offset] = ls->GetStoreType ();
memcpy (m_Buffer + offset + 1, ls->GetBuffer (), ls->GetBufferLen ());
dotnet::crypto::ChaCha20 (m_Buffer + offset, lenInnerPlaintext, keys2, keys2 + 32, m_Buffer + offset); // encrypt Layer 2
offset += lenInnerPlaintext;
dotnet::crypto::ChaCha20 (outerPlainText, lenOuterPlaintext, keys1, keys1 + 32, outerPlainText); // encrypt Layer 1
// signature
blindedSigner->Sign (m_Buffer, offset, m_Buffer + offset);
// store hash
m_StoreHash = blindedKey.GetStoreHash (date);
}
LocalEncryptedLeaseSet2::LocalEncryptedLeaseSet2 (std::shared_ptr<const IdentityEx> identity, const uint8_t * buf, size_t len):
LocalLeaseSet2 (NETDB_STORE_TYPE_ENCRYPTED_LEASESET2, identity, buf, len)
{
// fill inner LeaseSet2
auto blindedKey = std::make_shared<BlindedPublicKey>(identity);
dotnet::data::LeaseSet2 ls (buf, len, blindedKey); // inner layer
if (ls.IsValid ())
{
m_InnerLeaseSet = std::make_shared<LocalLeaseSet2>(ls.GetStoreType (), identity, ls.GetBuffer (), ls.GetBufferLen ());
m_StoreHash = blindedKey->GetStoreHash ();
}
else
LogPrint (eLogError, "LeaseSet2: couldn't extract inner layer");
}
}
}

297
libdotnet/LeaseSet.h Normal file
View file

@ -0,0 +1,297 @@
#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"
#include "DotNetEndian.h"
namespace dotnet
{
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; // transient
/* 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 = dotnet::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 size_t LEASE2_SIZE = 40; // 32 + 4 + 4
const uint8_t MAX_NUM_LEASES = 16;
const uint8_t NETDB_STORE_TYPE_LEASESET = 1;
class LeaseSet: public RoutingDestination
{
public:
LeaseSet (const uint8_t * buf, size_t len, bool storeLeases = true);
virtual ~LeaseSet () { delete[] m_EncryptionKey; delete[] m_Buffer; };
virtual void Update (const uint8_t * buf, size_t len, bool verifySignature = true);
bool IsNewer (const uint8_t * buf, size_t len) const;
void PopulateLeases (); // from buffer
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); };
virtual uint8_t GetStoreType () const { return NETDB_STORE_TYPE_LEASESET; };
virtual uint8_t GetOrigStoreType () const { return NETDB_STORE_TYPE_LEASESET; };
virtual uint32_t GetPublishedTimestamp () const { return 0; }; // should be set for LeaseSet2 only
virtual std::shared_ptr<const dotnet::crypto::Verifier> GetTransientVerifier () const { return nullptr; };
// implements RoutingDestination
std::shared_ptr<const IdentityEx> GetIdentity () const { return m_Identity; };
void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const;
bool IsDestination () const { return true; };
protected:
void UpdateLeasesBegin ();
void UpdateLeasesEnd ();
void UpdateLease (const Lease& lease, uint64_t ts);
// called from LeaseSet2
LeaseSet (bool storeLeases);
void SetBuffer (const uint8_t * buf, size_t len);
void SetIdentity (std::shared_ptr<const IdentityEx> identity) { m_Identity = identity; };
void SetExpirationTime (uint64_t t) { m_ExpirationTime = t; };
void SetIsValid (bool isValid) { m_IsValid = isValid; };
bool IsStoreLeases () const { return m_StoreLeases; };
private:
void ReadFromBuffer (bool readIdentity = true, bool verifySignature = true);
virtual uint64_t ExtractTimestamp (const uint8_t * buf, size_t len) const; // returns max 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;
uint8_t * m_Buffer;
size_t m_BufferLen;
};
/**
validate lease set buffer signature and extract expiration timestamp
@returns true if the leaseset is well formed and signature is valid
*/
bool LeaseSetBufferValidate(const uint8_t * ptr, size_t sz, uint64_t & expires);
const uint8_t NETDB_STORE_TYPE_STANDARD_LEASESET2 = 3;
const uint8_t NETDB_STORE_TYPE_ENCRYPTED_LEASESET2 = 5;
const uint8_t NETDB_STORE_TYPE_META_LEASESET2 = 7;
const uint16_t LEASESET2_FLAG_OFFLINE_KEYS = 0x0001;
class BlindedPublicKey // for encrypted LS2
{
public:
BlindedPublicKey (std::shared_ptr<const IdentityEx> identity, SigningKeyType blindedKeyType = dotnet::data::SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519);
BlindedPublicKey (const std::string& b33); // from b33 without .b32.dotnet
std::string ToB33 () const;
const uint8_t * GetPublicKey () const { return m_PublicKey.data (); };
size_t GetPublicKeyLen () const { return m_PublicKey.size (); };
SigningKeyType GetSigType () const { return m_SigType; };
SigningKeyType GetBlindedSigType () const { return m_BlindedSigType; };
void GetSubcredential (const uint8_t * blinded, size_t len, uint8_t * subcredential) const; // 32 bytes
void GetBlindedKey (const char * date, uint8_t * blindedKey) const; // blinded key 32 bytes, date is 8 chars "YYYYMMDD"
void BlindPrivateKey (const uint8_t * priv, const char * date, uint8_t * blindedPriv, uint8_t * blindedPub) const; // blinded key 32 bytes, date is 8 chars "YYYYMMDD"
dotnet::data::IdentHash GetStoreHash (const char * date = nullptr) const; // date is 8 chars "YYYYMMDD", use current if null
private:
void GetCredential (uint8_t * credential) const; // 32 bytes
void GenerateAlpha (const char * date, uint8_t * seed) const; // 64 bytes, date is 8 chars "YYYYMMDD"
void H (const std::string& p, const std::vector<std::pair<const uint8_t *, size_t> >& bufs, uint8_t * hash) const;
private:
std::vector<uint8_t> m_PublicKey;
dotnet::data::SigningKeyType m_SigType, m_BlindedSigType;
};
class LeaseSet2: public LeaseSet
{
public:
LeaseSet2 (uint8_t storeType, const uint8_t * buf, size_t len, bool storeLeases = true);
LeaseSet2 (const uint8_t * buf, size_t len, std::shared_ptr<const BlindedPublicKey> key); // store type 5, called from local netdb only
uint8_t GetStoreType () const { return m_StoreType; };
uint8_t GetOrigStoreType () const { return m_OrigStoreType; };
uint32_t GetPublishedTimestamp () const { return m_PublishedTimestamp; };
std::shared_ptr<const dotnet::crypto::Verifier> GetTransientVerifier () const { return m_TransientVerifier; };
void Update (const uint8_t * buf, size_t len, bool verifySignature);
// implements RoutingDestination
void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const;
private:
void ReadFromBuffer (const uint8_t * buf, size_t len, bool readIdentity = true, bool verifySignature = true);
void ReadFromBufferEncrypted (const uint8_t * buf, size_t len, std::shared_ptr<const BlindedPublicKey> key);
size_t ReadStandardLS2TypeSpecificPart (const uint8_t * buf, size_t len);
size_t ReadMetaLS2TypeSpecificPart (const uint8_t * buf, size_t len);
template<typename Verifier>
bool VerifySignature (Verifier& verifier, const uint8_t * buf, size_t len, size_t signatureOffset);
uint64_t ExtractTimestamp (const uint8_t * buf, size_t len) const;
private:
uint8_t m_StoreType, m_OrigStoreType;
uint32_t m_PublishedTimestamp = 0;
std::shared_ptr<dotnet::crypto::Verifier> m_TransientVerifier;
std::shared_ptr<dotnet::crypto::CryptoKeyEncryptor> m_Encryptor; // for standardLS2
};
// also called from Streaming.cpp
template<typename Verifier>
std::shared_ptr<dotnet::crypto::Verifier> ProcessOfflineSignature (const Verifier& verifier, const uint8_t * buf, size_t len, size_t& offset)
{
if (offset + 6 >= len) return nullptr;
const uint8_t * signedData = buf + offset;
uint32_t expiresTimestamp = bufbe32toh (buf + offset); offset += 4; // expires timestamp
if (expiresTimestamp < dotnet::util::GetSecondsSinceEpoch ()) return nullptr;
uint16_t keyType = bufbe16toh (buf + offset); offset += 2;
std::shared_ptr<dotnet::crypto::Verifier> transientVerifier (dotnet::data::IdentityEx::CreateVerifier (keyType));
if (!transientVerifier) return nullptr;
auto keyLen = transientVerifier->GetPublicKeyLen ();
if (offset + keyLen >= len) return nullptr;
transientVerifier->SetPublicKey (buf + offset); offset += keyLen;
if (offset + verifier->GetSignatureLen () >= len) return nullptr;
if (!verifier->Verify (signedData, keyLen + 6, buf + offset)) return nullptr;
offset += verifier->GetSignatureLen ();
return transientVerifier;
}
//------------------------------------------------------------------------------------
class LocalLeaseSet
{
public:
LocalLeaseSet (std::shared_ptr<const IdentityEx> identity, const uint8_t * encryptionPublicKey, std::vector<std::shared_ptr<dotnet::tunnel::InboundTunnel> > tunnels);
LocalLeaseSet (std::shared_ptr<const IdentityEx> identity, const uint8_t * buf, size_t len);
virtual ~LocalLeaseSet () { delete[] m_Buffer; };
virtual uint8_t * GetBuffer () const { return m_Buffer; };
uint8_t * GetSignature () { return GetBuffer () + GetBufferLen () - GetSignatureLen (); };
virtual 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 (); };
std::shared_ptr<const IdentityEx> GetIdentity () const { return m_Identity; };
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 GetBufferLen () == other.GetBufferLen () && !memcmp (GetBuffer (), other.GetBuffer (), GetBufferLen ()); };
virtual uint8_t GetStoreType () const { return NETDB_STORE_TYPE_LEASESET; };
virtual const IdentHash& GetStoreHash () const { return GetIdentHash (); }; // differ from ident hash for encrypted LeaseSet2
virtual std::shared_ptr<const LocalLeaseSet> GetInnerLeaseSet () const { return nullptr; }; // non-null for encrypted LeaseSet2
private:
uint64_t m_ExpirationTime; // in milliseconds
std::shared_ptr<const IdentityEx> m_Identity;
uint8_t * m_Buffer, * m_Leases;
size_t m_BufferLen;
};
class LocalLeaseSet2: public LocalLeaseSet
{
public:
LocalLeaseSet2 (uint8_t storeType, const dotnet::data::PrivateKeys& keys,
uint16_t keyType, uint16_t keyLen, const uint8_t * encryptionPublicKey,
std::vector<std::shared_ptr<dotnet::tunnel::InboundTunnel> > tunnels);
LocalLeaseSet2 (uint8_t storeType, std::shared_ptr<const IdentityEx> identity, const uint8_t * buf, size_t len); // from DNCP
virtual ~LocalLeaseSet2 () { delete[] m_Buffer; };
uint8_t * GetBuffer () const { return m_Buffer + 1; };
size_t GetBufferLen () const { return m_BufferLen; };
uint8_t GetStoreType () const { return m_Buffer[0]; };
protected:
LocalLeaseSet2 (std::shared_ptr<const IdentityEx> identity): LocalLeaseSet (identity, nullptr, 0), m_Buffer (nullptr), m_BufferLen(0) {}; // called from LocalEncryptedLeaseSet2
protected:
uint8_t * m_Buffer; // 1 byte store type + actual buffer
size_t m_BufferLen;
};
class LocalEncryptedLeaseSet2: public LocalLeaseSet2
{
public:
LocalEncryptedLeaseSet2 (std::shared_ptr<const LocalLeaseSet2> ls, const dotnet::data::PrivateKeys& keys, dotnet::data::SigningKeyType blindedKeyType = dotnet::data::SIGNING_KEY_TYPE_REDDSA_SHA512_ED25519);
LocalEncryptedLeaseSet2 (std::shared_ptr<const IdentityEx> identity, const uint8_t * buf, size_t len); // from DNCP
const IdentHash& GetStoreHash () const { return m_StoreHash; };
std::shared_ptr<const LocalLeaseSet> GetInnerLeaseSet () const { return m_InnerLeaseSet; };
private:
IdentHash m_StoreHash;
std::shared_ptr<const LocalLeaseSet2> m_InnerLeaseSet;
};
}
}
#endif

242
libdotnet/LittleBigEndian.h Normal file
View 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

241
libdotnet/Log.cpp Normal file
View file

@ -0,0 +1,241 @@
/*
* Copyright (c) 2013-2016, The PurpleI2P Project
*
* This file is part of Purple dotnet project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*/
#include "Log.h"
//for std::transform
#include <algorithm>
namespace dotnet {
namespace log {
static Log logger;
/**
* @brief Maps our loglevel to their symbolic name
*/
static const char * g_LogLevelStr[eNumLogLevels] =
{
"none", // eLogNone
"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[] = {
[eLogNone] = "\033[0m", /* reset */
[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 eLogNone : priority = LOG_CRIT; break;
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_TimeFormat("%H:%M:%S"),
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;
}
}
std::string str_tolower(std::string s) {
std::transform(s.begin(), s.end(), s.begin(),
// static_cast<int(*)(int)>(std::tolower) // wrong
// [](int c){ return std::tolower(c); } // wrong
// [](char c){ return std::tolower(c); } // wrong
[](unsigned char c){ return std::tolower(c); } // correct
);
return s;
}
void Log::SetLogLevel (const std::string& level_) {
std::string level=str_tolower(level_);
if (level == "none") { m_MinLevel = eLogNone; }
else 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), m_TimeFormat.c_str(), 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<dotnet::log::LogMsg> & msg)
{
m_Queue.Put(msg);
}
void Log::SendTo (const std::string& path)
{
if (m_LogStream) m_LogStream = nullptr; // close previous
if (m_MinLevel == eLogNone) return;
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) {
if (m_MinLevel == eLogNone) return;
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
} // dotnet

194
libdotnet/Log.h Normal file
View file

@ -0,0 +1,194 @@
/*
* Copyright (c) 2013-2016, The PurpleI2P Project
*
* This file is part of Purple dotnet 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
{
eLogNone = 0,
eLogError,
eLogWarning,
eLogInfo,
eLogDebug,
eNumLogLevels
};
enum LogType {
eLogStdout = 0,
eLogStream,
eLogFile,
#ifndef _WIN32
eLogSyslog,
#endif
};
namespace dotnet {
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];
dotnet::util::Queue<std::shared_ptr<LogMsg> > m_Queue;
bool m_HasColors;
std::string m_TimeFormat;
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);
/**
* @brief Sets format for timestamps in log
* @param format String with timestamp format
*/
void SetTimeFormat (std::string format) { m_TimeFormat = format; };
#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<dotnet::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
{
dotnet::log::Log &log = dotnet::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<dotnet::log::LogMsg>(level, std::time(nullptr), ss.str());
msg->tid = std::this_thread::get_id();
log.Append(msg);
}
#endif // LOG_H__

1406
libdotnet/NTCP2.cpp Normal file

File diff suppressed because it is too large Load diff

269
libdotnet/NTCP2.h Normal file
View file

@ -0,0 +1,269 @@
/*
* Copyright (c) 2013-2018, The PurpleI2P Project
*
* This file is part of Purple dotnet project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*
* Kovri go write your own code
*
*/
#ifndef NTCP2_H__
#define NTCP2_H__
#include <inttypes.h>
#include <memory>
#include <thread>
#include <list>
#include <map>
#include <array>
#include <openssl/bn.h>
#include <openssl/evp.h>
#include <boost/asio.hpp>
#include "Crypto.h"
#include "util.h"
#include "RouterInfo.h"
#include "TransportSession.h"
namespace dotnet
{
namespace transport
{
const size_t NTCP2_UNENCRYPTED_FRAME_MAX_SIZE = 65519;
const int NTCP2_MAX_PADDING_RATIO = 6; // in %
const int NTCP2_CONNECT_TIMEOUT = 5; // 5 seconds
const int NTCP2_ESTABLISH_TIMEOUT = 10; // 10 seconds
const int NTCP2_TERMINATION_TIMEOUT = 120; // 2 minutes
const int NTCP2_TERMINATION_CHECK_TIMEOUT = 30; // 30 seconds
const int NTCP2_CLOCK_SKEW = 60; // in seconds
const int NTCP2_MAX_OUTGOING_QUEUE_SIZE = 500; // how many messages we can queue up
enum NTCP2BlockType
{
eNTCP2BlkDateTime = 0,
eNTCP2BlkOptions, // 1
eNTCP2BlkRouterInfo, // 2
eNTCP2BlkDNNPMessage, // 3
eNTCP2BlkTermination, // 4
eNTCP2BlkPadding = 254
};
enum NTCP2TerminationReason
{
eNTCP2NormalClose = 0,
eNTCP2TerminationReceived, // 1
eNTCP2IdleTimeout, // 2
eNTCP2RouterShutdown, // 3
eNTCP2DataPhaseAEADFailure, // 4
eNTCP2IncompatibleOptions, // 5
eNTCP2IncompatibleSignatureType, // 6
eNTCP2ClockSkew, // 7
eNTCP2PaddingViolation, // 8
eNTCP2AEADFramingError, // 9
eNTCP2PayloadFormatError, // 10
eNTCP2Message1Error, // 11
eNTCP2Message2Error, // 12
eNTCP2Message3Error, // 13
eNTCP2IntraFrameReadTimeout, // 14
eNTCP2RouterInfoSignatureVerificationFail, // 15
eNTCP2IncorrectSParameter, // 16
eNTCP2Banned, // 17
};
// RouterInfo flags
const uint8_t NTCP2_ROUTER_INFO_FLAG_REQUEST_FLOOD = 0x01;
struct NTCP2Establisher
{
NTCP2Establisher ();
~NTCP2Establisher ();
const uint8_t * GetPub () const { return m_EphemeralKeys.GetPublicKey (); };
const uint8_t * GetRemotePub () const { return m_RemoteEphemeralPublicKey; }; // Y for Alice and X for Bob
uint8_t * GetRemotePub () { return m_RemoteEphemeralPublicKey; }; // to set
const uint8_t * GetK () const { return m_K; };
const uint8_t * GetCK () const { return m_CK; };
const uint8_t * GetH () const { return m_H; };
void KDF1Alice ();
void KDF1Bob ();
void KDF2Alice ();
void KDF2Bob ();
void KDF3Alice (); // for SessionConfirmed part 2
void KDF3Bob ();
void MixKey (const uint8_t * inputKeyMaterial);
void MixHash (const uint8_t * buf, size_t len);
void KeyDerivationFunction1 (const uint8_t * pub, dotnet::crypto::X25519Keys& priv, const uint8_t * rs, const uint8_t * epub); // for SessionRequest, (pub, priv) for DH
void KeyDerivationFunction2 (const uint8_t * sessionRequest, size_t sessionRequestLen, const uint8_t * epub); // for SessionCreate
void CreateEphemeralKey ();
void CreateSessionRequestMessage ();
void CreateSessionCreatedMessage ();
void CreateSessionConfirmedMessagePart1 (const uint8_t * nonce);
void CreateSessionConfirmedMessagePart2 (const uint8_t * nonce);
bool ProcessSessionRequestMessage (uint16_t& paddingLen);
bool ProcessSessionCreatedMessage (uint16_t& paddingLen);
bool ProcessSessionConfirmedMessagePart1 (const uint8_t * nonce);
bool ProcessSessionConfirmedMessagePart2 (const uint8_t * nonce, uint8_t * m3p2Buf);
dotnet::crypto::X25519Keys m_EphemeralKeys;
uint8_t m_RemoteEphemeralPublicKey[32]; // x25519
uint8_t m_RemoteStaticKey[32], m_IV[16], m_H[32] /*h*/, m_CK[33] /*ck*/, m_K[32] /*k*/;
dotnet::data::IdentHash m_RemoteIdentHash;
uint16_t m3p2Len;
uint8_t * m_SessionRequestBuffer, * m_SessionCreatedBuffer, * m_SessionConfirmedBuffer;
size_t m_SessionRequestBufferLen, m_SessionCreatedBufferLen;
};
class NTCP2Server;
class NTCP2Session: public TransportSession, public std::enable_shared_from_this<NTCP2Session>
{
public:
NTCP2Session (NTCP2Server& server, std::shared_ptr<const dotnet::data::RouterInfo> in_RemoteRouter = nullptr);
~NTCP2Session ();
void Terminate ();
void TerminateByTimeout ();
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 (); // Alice
void ServerLogin (); // Bob
void SendLocalRouterInfo (); // after handshake
void SendDNNPMessages (const std::vector<std::shared_ptr<DNNPMessage> >& msgs);
private:
void Established ();
void CreateNonce (uint64_t seqn, uint8_t * nonce);
void KeyDerivationFunctionDataPhase ();
void SetSipKeys (const uint8_t * sendSipKey, const uint8_t * receiveSipKey);
// establish
void SendSessionRequest ();
void SendSessionCreated ();
void SendSessionConfirmed ();
void HandleSessionRequestSent (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void HandleSessionRequestReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void HandleSessionRequestPaddingReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void HandleSessionCreatedSent (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void HandleSessionCreatedReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void HandleSessionCreatedPaddingReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void HandleSessionConfirmedSent (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void HandleSessionConfirmedReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
// data
void ReceiveLength ();
void HandleReceivedLength (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void Receive ();
void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void ProcessNextFrame (const uint8_t * frame, size_t len);
void SetNextSentFrameLength (size_t frameLen, uint8_t * lengthBuf);
void SendDNNPMsgs (std::vector<std::shared_ptr<DNNPMessage> >& msgs);
void HandleDNNPMsgsSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::vector<std::shared_ptr<DNNPMessage> > msgs);
void EncryptAndSendNextBuffer (size_t payloadLen);
void HandleNextFrameSent (const boost::system::error_code& ecode, std::size_t bytes_transferred);
size_t CreatePaddingBlock (size_t msgLen, uint8_t * buf, size_t len);
void SendQueue ();
void SendRouterInfo ();
void SendTermination (NTCP2TerminationReason reason);
void SendTerminationAndTerminate (NTCP2TerminationReason reason);
void PostDNNPMessages (std::vector<std::shared_ptr<DNNPMessage> > msgs);
private:
NTCP2Server& m_Server;
boost::asio::ip::tcp::socket m_Socket;
bool m_IsEstablished, m_IsTerminated;
std::unique_ptr<NTCP2Establisher> m_Establisher;
// data phase
uint8_t m_Kab[33], m_Kba[32], m_Sipkeysab[33], m_Sipkeysba[32];
const uint8_t * m_SendKey, * m_ReceiveKey;
#if OPENSSL_SIPHASH
EVP_PKEY * m_SendSipKey, * m_ReceiveSipKey;
EVP_MD_CTX * m_SendMDCtx, * m_ReceiveMDCtx;
#else
const uint8_t * m_SendSipKey, * m_ReceiveSipKey;
#endif
uint16_t m_NextReceivedLen;
uint8_t * m_NextReceivedBuffer, * m_NextSendBuffer;
union
{
uint8_t buf[8];
uint16_t key;
} m_ReceiveIV, m_SendIV;
uint64_t m_ReceiveSequenceNumber, m_SendSequenceNumber;
dotnet::DNNPMessagesHandler m_Handler;
bool m_IsSending;
std::list<std::shared_ptr<DNNPMessage> > m_SendQueue;
};
class NTCP2Server
{
public:
NTCP2Server ();
~NTCP2Server ();
void Start ();
void Stop ();
bool AddNTCP2Session (std::shared_ptr<NTCP2Session> session);
void RemoveNTCP2Session (std::shared_ptr<NTCP2Session> session);
std::shared_ptr<NTCP2Session> FindNTCP2Session (const dotnet::data::IdentHash& ident);
boost::asio::io_service& GetService () { return m_Service; };
void Connect(const boost::asio::ip::address & address, uint16_t port, std::shared_ptr<NTCP2Session> conn);
private:
void Run ();
void HandleAccept (std::shared_ptr<NTCP2Session> conn, const boost::system::error_code& error);
void HandleAcceptV6 (std::shared_ptr<NTCP2Session> conn, const boost::system::error_code& error);
void HandleConnect (const boost::system::error_code& ecode, std::shared_ptr<NTCP2Session> 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;
std::unique_ptr<boost::asio::ip::tcp::acceptor> m_NTCP2Acceptor, m_NTCP2V6Acceptor;
std::map<dotnet::data::IdentHash, std::shared_ptr<NTCP2Session> > m_NTCP2Sessions;
std::list<std::shared_ptr<NTCP2Session> > m_PendingIncomingSessions;
public:
// for HTTP/DotNetControl
const decltype(m_NTCP2Sessions)& GetNTCP2Sessions () const { return m_NTCP2Sessions; };
};
}
}
#endif

1314
libdotnet/NTCPSession.cpp Normal file

File diff suppressed because it is too large Load diff

234
libdotnet/NTCPSession.h Normal file
View file

@ -0,0 +1,234 @@
#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 "DNNPProtocol.h"
#include "TransportSession.h"
#include "CryptoWorker.h"
namespace dotnet
{
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;
};
struct NTCPWork;
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*/ + dotnet::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 dotnet::data::RouterInfo> in_RemoteRouter = nullptr);
~NTCPSession ();
void Terminate ();
void Done ();
boost::asio::ip::tcp::socket& GetSocket () { return m_Socket; };
boost::asio::io_service & GetService();
bool IsEstablished () const { return m_IsEstablished; };
bool IsTerminated () const { return m_IsTerminated; };
void ClientLogin ();
void ServerLogin ();
void SendDNNPMessages (const std::vector<std::shared_ptr<DNNPMessage> >& msgs);
private:
void PostDNNPMessages (std::vector<std::shared_ptr<DNNPMessage> > 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 (NTCPWork * work=nullptr);
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 (NTCPWork * work=nullptr);
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<dotnet::DNNPMessage> msg);
boost::asio::const_buffers_1 CreateMsgBuffer (std::shared_ptr<DNNPMessage> msg);
void Send (const std::vector<std::shared_ptr<DNNPMessage> >& msgs);
void HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::vector<std::shared_ptr<DNNPMessage> > msgs);
private:
NTCPServer& m_Server;
boost::asio::ip::tcp::socket m_Socket;
bool m_IsEstablished, m_IsTerminated;
dotnet::crypto::CBCDecryption m_Decryption;
dotnet::crypto::CBCEncryption m_Encryption;
struct Establisher
{
NTCPPhase1 phase1;
NTCPPhase2 phase2;
} * m_Establisher;
dotnet::crypto::AESAlignedBuffer<NTCP_BUFFER_SIZE + 16> m_ReceiveBuffer;
dotnet::crypto::AESAlignedBuffer<16> m_TimeSyncBuffer;
int m_ReceiveBufferOffset;
std::shared_ptr<DNNPMessage> m_NextMessage;
size_t m_NextMessageOffset;
dotnet::DNNPMessagesHandler m_Handler;
bool m_IsSending;
std::vector<std::shared_ptr<DNNPMessage> > m_SendQueue;
};
// TODO: move to NTCP.h/.cpp
class NTCPServer
{
public:
typedef dotnet::worker::ThreadPool<NTCPSession> Pool;
enum RemoteAddressType
{
eIP4Address,
eIP6Address,
eHostname
};
enum ProxyType
{
eNoProxy,
eSocksProxy,
eHTTPProxy
};
NTCPServer (int workers=4);
~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 dotnet::data::IdentHash& ident);
void ConnectWithProxy (const std::string& addr, uint16_t port, RemoteAddressType addrtype, std::shared_ptr<NTCPSession> conn);
void Connect(const boost::asio::ip::address & address, uint16_t port, std::shared_ptr<NTCPSession> conn);
bool IsBoundV4() const { return m_NTCPAcceptor != nullptr; };
bool IsBoundV6() const { return m_NTCPV6Acceptor != nullptr; };
bool NetworkIsReady() const { return IsBoundV4() || IsBoundV6() || UsingProxy(); };
bool UsingProxy() const { return m_ProxyType != eNoProxy; };
void UseProxy(ProxyType proxy, const std::string & address, uint16_t port);
boost::asio::io_service& GetService () { return m_Service; };
void SetSessionLimits(uint16_t softLimit, uint16_t hardLimit) { m_SoftLimit = softLimit; m_HardLimit = hardLimit; }
bool ShouldLimit() const { return ShouldHardLimit() || ShouldSoftLimit(); }
void Work(std::shared_ptr<NTCPSession> conn, Pool::WorkFunc work)
{
m_CryptoPool->Offer({conn, work});
}
private:
/** @brief return true for hard limit */
bool ShouldHardLimit() const { return m_HardLimit && m_NTCPSessions.size() >= m_HardLimit; }
/** @brief return true for probabalistic soft backoff */
bool ShouldSoftLimit() const
{
auto sessions = m_NTCPSessions.size();
return sessions && m_SoftLimit && m_SoftLimit < sessions && ( rand() % sessions ) <= m_SoftLimit;
}
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);
void HandleProxyConnect(const boost::system::error_code& ecode, std::shared_ptr<NTCPSession> conn, std::shared_ptr<boost::asio::deadline_timer> timer, const std::string & host, uint16_t port, RemoteAddressType adddrtype);
void AfterSocksHandshake(std::shared_ptr<NTCPSession> conn, std::shared_ptr<boost::asio::deadline_timer> timer, const std::string & host, uint16_t port, RemoteAddressType adddrtype);
// 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<dotnet::data::IdentHash, std::shared_ptr<NTCPSession> > m_NTCPSessions; // access from m_Thread only
std::list<std::shared_ptr<NTCPSession> > m_PendingIncomingSessions;
ProxyType m_ProxyType;
std::string m_ProxyAddress;
uint16_t m_ProxyPort;
boost::asio::ip::tcp::resolver m_Resolver;
boost::asio::ip::tcp::endpoint * m_ProxyEndpoint;
std::shared_ptr<Pool> m_CryptoPool;
uint16_t m_SoftLimit, m_HardLimit;
public:
// for HTTP/DotNetControl
const decltype(m_NTCPSessions)& GetNTCPSessions () const { return m_NTCPSessions; };
};
}
}
#endif

1254
libdotnet/NetDb.cpp Normal file

File diff suppressed because it is too large Load diff

162
libdotnet/NetDb.hpp Normal file
View file

@ -0,0 +1,162 @@
#ifndef NETDB_H__
#define NETDB_H__
// this file is called NetDb.hpp to resolve conflict with libc's netdb.h on case insensitive fs
#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 "DNNPProtocol.h"
#include "RouterInfo.h"
#include "LeaseSet.h"
#include "Tunnel.h"
#include "TunnelPool.h"
#include "Reseed.h"
#include "NetDbRequests.h"
#include "Family.h"
namespace dotnet
{
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 dotnet::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 dotnet::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);
bool AddLeaseSet2 (const IdentHash& ident, const uint8_t * buf, int len, uint8_t storeType);
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 DNNPMessage> msg);
void HandleDatabaseSearchReplyMsg (std::shared_ptr<const DNNPMessage> msg);
void HandleDatabaseLookupMsg (std::shared_ptr<const DNNPMessage> msg);
void HandleNTCP2RouterInfoMsg (std::shared_ptr<const DNNPMessage> m);
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 PostDNNPMsg (std::shared_ptr<const DNNPMessage> 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 Flood (const IdentHash& ident, std::shared_ptr<DNNPMessage> floodMsg);
void ManageLeaseSets ();
void ManageRequests ();
void ReseedFromFloodfill(const RouterInfo & ri, int numRouters=40, int numFloodfills=20);
std::shared_ptr<const RouterInfo> AddRouterInfo (const uint8_t * buf, int len, bool& updated);
std::shared_ptr<const RouterInfo> AddRouterInfo (const IdentHash& ident, const uint8_t * buf, int len, bool& updated);
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;
dotnet::util::Queue<std::shared_ptr<const DNNPMessage> > m_Queue; // of DNNPDatabaseStoreMsg
GzipInflator m_Inflator;
Reseeder * m_Reseeder;
Families m_Families;
dotnet::fs::HashedStorage m_Storage;
friend class NetDbRequests;
NetDbRequests m_Requests;
bool m_PersistProfiles;
/** 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
libdotnet/NetDbRequests.cpp Normal file
View file

@ -0,0 +1,161 @@
#include "Log.h"
#include "DNNPProtocol.h"
#include "Transports.h"
#include "NetDb.hpp"
#include "NetDbRequests.h"
namespace dotnet
{
namespace data
{
std::shared_ptr<DNNPMessage> RequestedDestination::CreateRequestMessage (std::shared_ptr<const RouterInfo> router,
std::shared_ptr<const dotnet::tunnel::InboundTunnel> replyTunnel)
{
std::shared_ptr<DNNPMessage> msg;
if(replyTunnel)
msg = dotnet::CreateRouterInfoDatabaseLookupMsg (m_Destination,
replyTunnel->GetNextIdentHash (), replyTunnel->GetNextTunnelID (), m_IsExploratory,
&m_ExcludedPeers);
else
msg = dotnet::CreateRouterInfoDatabaseLookupMsg(m_Destination, dotnet::context.GetIdentHash(), 0, m_IsExploratory, &m_ExcludedPeers);
if(router)
m_ExcludedPeers.insert (router->GetIdentHash ());
m_CreationTime = dotnet::util::GetSecondsSinceEpoch ();
return msg;
}
std::shared_ptr<DNNPMessage> RequestedDestination::CreateRequestMessage (const IdentHash& floodfill)
{
auto msg = dotnet::CreateRouterInfoDatabaseLookupMsg (m_Destination,
dotnet::context.GetRouterInfo ().GetIdentHash () , 0, false, &m_ExcludedPeers);
m_ExcludedPeers.insert (floodfill);
m_CreationTime = dotnet::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 = dotnet::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 = dotnet::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
libdotnet/NetDbRequests.h Normal file
View 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 dotnet
{
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<DNNPMessage> CreateRequestMessage (std::shared_ptr<const RouterInfo>, std::shared_ptr<const dotnet::tunnel::InboundTunnel> replyTunnel);
std::shared_ptr<DNNPMessage> 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

25
libdotnet/Poly1305.cpp Normal file
View file

@ -0,0 +1,25 @@
#include "Poly1305.h"
/**
This code is licensed under the MCGSI Public License
Copyright 2018 Jeff Becker
Kovri go write your own code
*/
#if !OPENSSL_AEAD_CHACHA20_POLY1305
namespace dotnet
{
namespace crypto
{
void Poly1305HMAC(uint64_t * out, const uint64_t * key, const uint8_t * buf, std::size_t sz)
{
Poly1305 p(key);
p.Update(buf, sz);
p.Finish(out);
}
}
}
#endif

261
libdotnet/Poly1305.h Normal file
View file

@ -0,0 +1,261 @@
/**
This code is licensed under the MCGSI Public License
Copyright 2018 Jeff Becker
Kovri go write your own code
*/
#ifndef LIBDOTNET_POLY1305_H
#define LIBDOTNET_POLY1305_H
#include <cstdint>
#include <cstring>
#include "Crypto.h"
#if !OPENSSL_AEAD_CHACHA20_POLY1305
namespace dotnet
{
namespace crypto
{
const std::size_t POLY1305_DIGEST_BYTES = 16;
const std::size_t POLY1305_DIGEST_DWORDS = 4;
const std::size_t POLY1305_KEY_BYTES = 32;
const std::size_t POLY1305_KEY_DWORDS = 8;
const std::size_t POLY1305_BLOCK_BYTES = 16;
namespace poly1305
{
struct LongBlock
{
unsigned long data[17];
operator unsigned long * ()
{
return data;
}
};
struct Block
{
unsigned char data[17];
void Zero()
{
memset(data, 0, sizeof(data));
}
operator uint8_t * ()
{
return data;
}
Block & operator += (const Block & other)
{
unsigned short u;
unsigned int i;
for(u = 0, i = 0; i < 17; i++)
{
u += (unsigned short) data[i] + (unsigned short) other.data[i];
data[i] = (unsigned char) u & 0xff;
u >>= 8;
}
return *this;
}
Block & operator %=(const LongBlock & other)
{
unsigned long u;
unsigned int i;
u = 0;
for (i = 0; i < 16; i++) {
u += other.data[i];
data[i] = (unsigned char)u & 0xff;
u >>= 8;
}
u += other.data[16];
data[16] = (unsigned char)u & 0x03;
u >>= 2;
u += (u << 2);
for (i = 0; i < 16; i++) {
u += data[i];
data[i] = (unsigned char)u & 0xff;
u >>= 8;
}
data[16] += (unsigned char)u;
return *this;
}
Block & operator = (const Block & other)
{
memcpy(data, other.data, sizeof(data));
return *this;
}
Block & operator ~ ()
{
static const Block minusp = {
0x05,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0xfc
};
Block orig;
unsigned char neg;
unsigned int i;
orig = *this;
*this += minusp;
neg = -(data[16] >> 7);
for(i = 0; i < 17; i++)
data[i] ^= neg & (orig.data[i] ^ data[i]);
return *this;
}
void PutKey(const uint64_t * key_l)
{
const uint8_t * key = (const uint8_t*) key_l;
data[0] = key[0] & 0xff;
data[1] = key[1] & 0xff;
data[2] = key[2] & 0xff;
data[3] = key[3] & 0x0f;
data[4] = key[4] & 0xfc;
data[5] = key[5] & 0xff;
data[6] = key[6] & 0xff;
data[7] = key[7] & 0x0f;
data[8] = key[8] & 0xfc;
data[9] = key[9] & 0xff;
data[10] = key[10] & 0xff;
data[11] = key[11] & 0x0f;
data[12] = key[12] & 0xfc;
data[13] = key[13] & 0xff;
data[14] = key[14] & 0xff;
data[15] = key[15] & 0x0f;
data[16] = 0;
}
template<typename Int_t>
void Put(const Int_t * d, uint8_t last=0)
{
memcpy(data, d, 16);
data[16] = last;
}
};
struct Buffer
{
uint8_t data[POLY1305_BLOCK_BYTES];
operator uint8_t * ()
{
return data;
}
};
}
struct Poly1305
{
Poly1305(const uint64_t * key)
{
m_Leftover = 0;
m_H.Zero();
m_Final = 0;
m_R.PutKey(key);
m_Pad.Put(key + 2);
}
void Update(const uint8_t * buf, size_t sz)
{
// process leftover
if(m_Leftover)
{
size_t want = POLY1305_BLOCK_BYTES - m_Leftover;
if(want > sz) want = sz;
memcpy(m_Buffer + m_Leftover, buf, want);
sz -= want;
buf += want;
m_Leftover += want;
if(m_Leftover < POLY1305_BLOCK_BYTES) return;
Blocks(m_Buffer, POLY1305_BLOCK_BYTES);
m_Leftover = 0;
}
// process blocks
if(sz >= POLY1305_BLOCK_BYTES)
{
size_t want = (sz & ~(POLY1305_BLOCK_BYTES - 1));
Blocks(buf, want);
buf += want;
sz -= want;
}
// leftover
if(sz)
{
memcpy(m_Buffer+m_Leftover, buf, sz);
m_Leftover += sz;
}
}
void Blocks(const uint8_t * buf, size_t sz)
{
const unsigned char hi = m_Final ^ 1;
while (sz >= POLY1305_BLOCK_BYTES) {
unsigned long u;
unsigned int i, j;
m_Msg.Put(buf, hi);
/* h += m */
m_H += m_Msg;
/* h *= r */
for (i = 0; i < 17; i++) {
u = 0;
for (j = 0; j <= i ; j++) {
u += (unsigned short)m_H.data[j] * m_R.data[i - j];
}
for (j = i + 1; j < 17; j++) {
unsigned long v = (unsigned short)m_H.data[j] * m_R.data[i + 17 - j];
v = ((v << 8) + (v << 6)); /* v *= (5 << 6); */
u += v;
}
m_HR[i] = u;
}
/* (partial) h %= p */
m_H %= m_HR;
buf += POLY1305_BLOCK_BYTES;
sz -= POLY1305_BLOCK_BYTES;
}
}
void Finish(uint64_t * out)
{
// process leftovers
if(m_Leftover)
{
size_t idx = m_Leftover;
m_Buffer[idx++] = 1;
for(; idx < POLY1305_BLOCK_BYTES; idx++)
m_Buffer[idx] = 0;
m_Final = 1;
Blocks(m_Buffer, POLY1305_BLOCK_BYTES);
}
// freeze H
~m_H;
// add pad
m_H += m_Pad;
// copy digest
memcpy(out, m_H, 16);
}
size_t m_Leftover;
poly1305::Buffer m_Buffer;
poly1305::Block m_H;
poly1305::Block m_R;
poly1305::Block m_Pad;
poly1305::Block m_Msg;
poly1305::LongBlock m_HR;
uint8_t m_Final;
};
void Poly1305HMAC(uint64_t * out, const uint64_t * key, const uint8_t * buf, std::size_t sz);
}
}
#endif
#endif

195
libdotnet/Profiling.cpp Normal file
View 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 dotnet
{
namespace data
{
dotnet::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 (!dotnet::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(dotnet::fs::GetDataDir());
m_ProfilesStorage.Init(dotnet::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);
dotnet::fs::Remove(path);
}
}
}
}
}

67
libdotnet/Profiling.h Normal file
View 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 dotnet
{
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
libdotnet/Queue.h Normal file
View 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 dotnet
{
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 (std::move(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

705
libdotnet/Reseed.cpp Normal file
View file

@ -0,0 +1,705 @@
#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 "DotNetEndian.h"
#include "Reseed.h"
#include "FS.h"
#include "Log.h"
#include "Identity.h"
#include "NetDb.hpp"
#include "HTTP.h"
#include "util.h"
#include "Config.h"
namespace dotnet
{
namespace data
{
Reseeder::Reseeder()
{
}
Reseeder::~Reseeder()
{
}
/** @brief tries to bootstrap into DOTNET network (from local files and servers, with respect of options)
*/
void Reseeder::Bootstrap ()
{
std::string su3FileName; dotnet::config::GetOption("reseed.file", su3FileName);
std::string zipFileName; dotnet::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; dotnet::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] + "dotnetseeds.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[]="DOTNETsu3";
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; dotnet::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, 512, n); // RSA 4096 assumed
BN_mod_exp (s, s, dotnet::crypto::GetRSAE (), n, bnctx); // s = s^e mod n
uint8_t * enSigBuf = new uint8_t[signatureLength];
dotnet::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 descriptor 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: Processing 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)
{
dotnet::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
{
dotnet::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 = dotnet::util::GetMillisecondsSinceEpoch ();
int numOutdated = 0;
dotnet::data::netdb.VisitRouterInfos (
[&numOutdated, ts](std::shared_ptr<const RouterInfo> r)
{
if (r && ts > r->GetTimestamp () + 10*dotnet::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"
" ***___***");
dotnet::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;
dotnet::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 = dotnet::fs::DataDirPath("certificates", "reseed");
std::vector<std::string> files;
int numCertificates = 0;
if (!dotnet::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)
{
dotnet::http::URL proxyUrl;
std::string proxy; dotnet::config::GetOption("reseed.proxy", proxy);
// check for proxy url
if(proxy.size()) {
// parse
if(proxyUrl.parse(proxy)) {
if (proxyUrl.schema == "http" && !proxyUrl.port) {
proxyUrl.port = 80;
} else if (proxyUrl.schema == "socks" && !proxyUrl.port) {
proxyUrl.port = 1080;
}
// check for valid proxy url schema
if (proxyUrl.schema != "http" && proxyUrl.schema != "socks") {
LogPrint(eLogError, "Reseed: bad proxy url: ", proxy);
return "";
}
} else {
LogPrint(eLogError, "Reseed: bad proxy url: ", proxy);
return "";
}
}
dotnet::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;
boost::asio::ssl::context ctx(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);
if(proxyUrl.schema.size())
{
// proxy connection
auto it = boost::asio::ip::tcp::resolver(service).resolve (
boost::asio::ip::tcp::resolver::query (proxyUrl.host, std::to_string(proxyUrl.port)), ecode);
if(!ecode)
{
s.lowest_layer().connect(*it, ecode);
if(!ecode)
{
auto & sock = s.next_layer();
if(proxyUrl.schema == "http")
{
dotnet::http::HTTPReq proxyReq;
dotnet::http::HTTPRes proxyRes;
proxyReq.method = "CONNECT";
proxyReq.version = "HTTP/1.1";
proxyReq.uri = url.host + ":" + std::to_string(url.port);
boost::asio::streambuf writebuf, readbuf;
std::ostream out(&writebuf);
out << proxyReq.to_string();
boost::asio::write(sock, writebuf.data(), boost::asio::transfer_all(), ecode);
if (ecode)
{
sock.close();
LogPrint(eLogError, "Reseed: HTTP CONNECT write error: ", ecode.message());
return "";
}
boost::asio::read_until(sock, readbuf, "\r\n\r\n", ecode);
if (ecode)
{
sock.close();
LogPrint(eLogError, "Reseed: HTTP CONNECT read error: ", ecode.message());
return "";
}
if(proxyRes.parse(boost::asio::buffer_cast<const char *>(readbuf.data()), readbuf.size()) <= 0)
{
sock.close();
LogPrint(eLogError, "Reseed: HTTP CONNECT malformed reply");
return "";
}
if(proxyRes.code != 200)
{
sock.close();
LogPrint(eLogError, "Reseed: HTTP CONNECT got bad status: ", proxyRes.code);
return "";
}
}
else
{
// assume socks if not http, is checked before this for other types
// TODO: support username/password auth etc
uint8_t hs_writebuf[3] = {0x05, 0x01, 0x00};
uint8_t hs_readbuf[2];
boost::asio::write(sock, boost::asio::buffer(hs_writebuf, 3), boost::asio::transfer_all(), ecode);
if(ecode)
{
sock.close();
LogPrint(eLogError, "Reseed: SOCKS handshake write failed: ", ecode.message());
return "";
}
boost::asio::read(sock, boost::asio::buffer(hs_readbuf, 2), ecode);
if(ecode)
{
sock.close();
LogPrint(eLogError, "Reseed: SOCKS handshake read failed: ", ecode.message());
return "";
}
size_t sz = 0;
uint8_t buf[256];
buf[0] = 0x05;
buf[1] = 0x01;
buf[2] = 0x00;
buf[3] = 0x03;
sz += 4;
size_t hostsz = url.host.size();
if(1 + 2 + hostsz + sz > sizeof(buf))
{
sock.close();
LogPrint(eLogError, "Reseed: SOCKS handshake failed, hostname too big: ", url.host);
return "";
}
buf[4] = (uint8_t) hostsz;
memcpy(buf+5, url.host.c_str(), hostsz);
sz += hostsz + 1;
htobe16buf(buf+sz, url.port);
sz += 2;
boost::asio::write(sock, boost::asio::buffer(buf, sz), boost::asio::transfer_all(), ecode);
if(ecode)
{
sock.close();
LogPrint(eLogError, "Reseed: SOCKS handshake failed writing: ", ecode.message());
return "";
}
boost::asio::read(sock, boost::asio::buffer(buf, 10), ecode);
if(ecode)
{
sock.close();
LogPrint(eLogError, "Reseed: SOCKS handshake failed reading: ", ecode.message());
return "";
}
if(buf[1] != 0x00)
{
sock.close();
LogPrint(eLogError, "Reseed: SOCKS handshake bad reply code: ", std::to_string(buf[1]));
return "";
}
}
}
}
}
else
{
// direct connection
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)
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);
dotnet::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();
dotnet::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 (!dotnet::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 ());
return "";
}
}
}

50
libdotnet/Reseed.h Normal file
View 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 dotnet
{
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

679
libdotnet/RouterContext.cpp Normal file
View file

@ -0,0 +1,679 @@
#include <fstream>
#include <openssl/rand.h>
#include "Config.h"
#include "Crypto.h"
#include "Ed25519.h"
#include "Timestamp.h"
#include "DNNPProtocol.h"
#include "NetDb.hpp"
#include "FS.h"
#include "util.h"
#include "version.h"
#include "Log.h"
#include "Family.h"
#include "RouterContext.h"
namespace dotnet
{
RouterContext context;
RouterContext::RouterContext ():
m_LastUpdateTime (0), m_AcceptsTunnels (true), m_IsFloodfill (false),
m_StartupTime (0), m_ShareRatio (100), m_Status (eRouterStatusOK),
m_Error (eRouterErrorNone), m_NetID (DOTNET_NET_ID)
{
}
void RouterContext::Init ()
{
srand (dotnet::util::GetMillisecondsSinceEpoch () % 1000);
m_StartupTime = dotnet::util::GetSecondsSinceEpoch ();
if (!Load ())
CreateNewRouter ();
m_Decryptor = m_Keys.CreateDecryptor (nullptr);
UpdateRouterInfo ();
}
void RouterContext::CreateNewRouter ()
{
m_Keys = dotnet::data::PrivateKeys::CreateRandomKeys (dotnet::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519);
SaveKeys ();
NewRouterInfo ();
}
void RouterContext::NewRouterInfo ()
{
dotnet::data::RouterInfo routerInfo;
routerInfo.SetRouterIdentity (GetIdentity ());
uint16_t port; dotnet::config::GetOption("port", port);
if (!port)
{
port = rand () % (30777 - 9111) + 9111; // DOTNET network ports range
if (port == 9150) port = 9151; // Tor browser
}
bool ipv4; dotnet::config::GetOption("ipv4", ipv4);
bool ipv6; dotnet::config::GetOption("ipv6", ipv6);
bool ssu; dotnet::config::GetOption("ssu", ssu);
bool ntcp; dotnet::config::GetOption("ntcp", ntcp);
bool ntcp2; dotnet::config::GetOption("ntcp2.enabled", ntcp2);
bool nat; dotnet::config::GetOption("nat", nat);
std::string ifname; dotnet::config::GetOption("ifname", ifname);
std::string ifname4; dotnet::config::GetOption("ifname4", ifname4);
std::string ifname6; dotnet::config::GetOption("ifname6", ifname6);
if (ipv4)
{
std::string host = "127.0.0.1";
if (!dotnet::config::IsDefault("host"))
dotnet::config::GetOption("host", host);
else if (!nat && !ifname.empty())
/* bind to interface, we have no NAT so set external address too */
host = dotnet::util::net::GetInterfaceAddress(ifname, false).to_string(); // v4
if(ifname4.size())
host = dotnet::util::net::GetInterfaceAddress(ifname4, false).to_string();
if (ssu)
routerInfo.AddSSUAddress (host.c_str(), port, routerInfo.GetIdentHash ());
if (ntcp)
routerInfo.AddNTCPAddress (host.c_str(), port);
}
if (ipv6)
{
std::string host = "::1";
if (!dotnet::config::IsDefault("host") && !ipv4) // override if v6 only
dotnet::config::GetOption("host", host);
else if (!ifname.empty())
host = dotnet::util::net::GetInterfaceAddress(ifname, true).to_string(); // v6
if(ifname6.size())
host = dotnet::util::net::GetInterfaceAddress(ifname6, true).to_string();
if (ssu)
routerInfo.AddSSUAddress (host.c_str(), port, routerInfo.GetIdentHash ());
if (ntcp)
routerInfo.AddNTCPAddress (host.c_str(), port);
}
routerInfo.SetCaps (dotnet::data::RouterInfo::eReachable |
dotnet::data::RouterInfo::eSSUTesting | dotnet::data::RouterInfo::eSSUIntroducer); // LR, BC
routerInfo.SetProperty ("netId", std::to_string (m_NetID));
routerInfo.SetProperty ("router.version", DOTNET_VERSION);
routerInfo.CreateBuffer (m_Keys);
m_RouterInfo.SetRouterIdentity (GetIdentity ());
m_RouterInfo.Update (routerInfo.GetBuffer (), routerInfo.GetBufferLen ());
if (ntcp2) // we don't store iv in the address if non published so we must update it from keys
{
if (!m_NTCP2Keys) NewNTCP2Keys ();
UpdateNTCP2Address (true);
if (!ntcp) // NTCP2 should replace NTCP
{
bool published; dotnet::config::GetOption("ntcp2.published", published);
if (published)
PublishNTCP2Address (port, true);
}
}
}
void RouterContext::UpdateRouterInfo ()
{
m_RouterInfo.CreateBuffer (m_Keys);
m_RouterInfo.SaveToFile (dotnet::fs::DataDirPath (ROUTER_INFO));
m_LastUpdateTime = dotnet::util::GetSecondsSinceEpoch ();
}
void RouterContext::NewNTCP2Keys ()
{
m_StaticKeys.reset (new dotnet::crypto::X25519Keys ());
m_StaticKeys->GenerateKeys ();
m_NTCP2Keys.reset (new NTCP2PrivateKeys ());
m_StaticKeys->GetPrivateKey (m_NTCP2Keys->staticPrivateKey);
memcpy (m_NTCP2Keys->staticPublicKey, m_StaticKeys->GetPublicKey (), 32);
RAND_bytes (m_NTCP2Keys->iv, 16);
// save
std::ofstream fk (dotnet::fs::DataDirPath (NTCP2_KEYS), std::ofstream::binary | std::ofstream::out);
fk.write ((char *)m_NTCP2Keys.get (), sizeof (NTCP2PrivateKeys));
}
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->IsNTCP2 () && address->port != port)
{
address->port = port;
updated = true;
}
}
if (updated)
UpdateRouterInfo ();
}
void RouterContext::PublishNTCP2Address (int port, bool publish)
{
if (!m_NTCP2Keys) return;
if (!port)
{
port = rand () % (30777 - 9111) + 9111; // DOTNET network ports range
if (port == 9150) port = 9151; // Tor browser
}
bool updated = false;
for (auto& address : m_RouterInfo.GetAddresses ())
{
if (address->IsNTCP2 () && (address->port != port || address->ntcp2->isPublished != publish))
{
address->port = port;
address->cost = publish ? 3 : 14;
address->ntcp2->isPublished = publish;
address->ntcp2->iv = m_NTCP2Keys->iv;
updated = true;
}
}
if (updated)
UpdateRouterInfo ();
}
void RouterContext::UpdateNTCP2Address (bool enable)
{
auto& addresses = m_RouterInfo.GetAddresses ();
bool found = false, updated = false;
for (auto it = addresses.begin (); it != addresses.end (); ++it)
{
if ((*it)->IsNTCP2 ())
{
found = true;
if (!enable)
{
addresses.erase (it);
updated= true;
}
break;
}
}
if (enable && !found)
{
m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv);
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 = dotnet::util::GetSecondsSinceEpoch ();
if (updated || ts > m_LastUpdateTime + ROUTER_INFO_UPDATE_INTERVAL)
UpdateRouterInfo ();
}
bool RouterContext::AddIntroducer (const dotnet::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 () | dotnet::data::RouterInfo::eFloodfill);
else
{
m_RouterInfo.SetCaps (m_RouterInfo.GetCaps () & ~dotnet::data::RouterInfo::eFloodfill);
// we don't publish number of routers and leaseset for non-floodfill
m_RouterInfo.DeleteProperty (dotnet::data::ROUTER_INFO_PROPERTY_LEASESETS);
m_RouterInfo.DeleteProperty (dotnet::data::ROUTER_INFO_PROPERTY_ROUTERS);
}
UpdateRouterInfo ();
}
std::string RouterContext::GetFamily () const
{
return m_RouterInfo.GetProperty (dotnet::data::ROUTER_INFO_PROPERTY_FAMILY);
}
void RouterContext::SetFamily (const std::string& family)
{
std::string signature;
if (family.length () > 0)
signature = dotnet::data::CreateFamilySignature (family, GetIdentHash ());
if (signature.length () > 0)
{
m_RouterInfo.SetProperty (dotnet::data::ROUTER_INFO_PROPERTY_FAMILY, family);
m_RouterInfo.SetProperty (dotnet::data::ROUTER_INFO_PROPERTY_FAMILY_SIG, signature);
}
else
{
m_RouterInfo.DeleteProperty (dotnet::data::ROUTER_INFO_PROPERTY_FAMILY);
m_RouterInfo.DeleteProperty (dotnet::data::ROUTER_INFO_PROPERTY_FAMILY_SIG);
}
}
void RouterContext::SetBandwidth (char L)
{
uint32_t limit = 0;
enum { low, high, extra, unlim } type = high;
/* detect parameters */
switch (L)
{
case dotnet::data::CAPS_FLAG_LOW_BANDWIDTH1 : limit = 12; type = low; break;
case dotnet::data::CAPS_FLAG_LOW_BANDWIDTH2 : limit = 48; type = low; break;
case dotnet::data::CAPS_FLAG_HIGH_BANDWIDTH1 : limit = 64; type = high; break;
case dotnet::data::CAPS_FLAG_HIGH_BANDWIDTH2 : limit = 128; type = high; break;
case dotnet::data::CAPS_FLAG_HIGH_BANDWIDTH3 : limit = 256; type = high; break;
case dotnet::data::CAPS_FLAG_EXTRA_BANDWIDTH1 : limit = 2048; type = extra; break;
case dotnet::data::CAPS_FLAG_EXTRA_BANDWIDTH2 : limit = 1000000; type = unlim; break; // 1Gbyte/s
default:
limit = 48; type = low;
}
/* update caps & flags in RI */
auto caps = m_RouterInfo.GetCaps ();
caps &= ~dotnet::data::RouterInfo::eHighBandwidth;
caps &= ~dotnet::data::RouterInfo::eExtraBandwidth;
switch (type)
{
case low : /* not set */; break;
case extra : caps |= dotnet::data::RouterInfo::eExtraBandwidth; break; // 'P'
case unlim : caps |= dotnet::data::RouterInfo::eExtraBandwidth; // no break here, extra + high means 'X'
case high : caps |= dotnet::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'); }
}
void RouterContext::SetShareRatio (int percents)
{
if (percents < 0) percents = 0;
if (percents > 100) percents = 100;
m_ShareRatio = percents;
}
bool RouterContext::IsUnreachable () const
{
return m_RouterInfo.GetCaps () & dotnet::data::RouterInfo::eUnreachable;
}
void RouterContext::PublishNTCPAddress (bool publish, bool v4only)
{
auto& addresses = m_RouterInfo.GetAddresses ();
if (publish)
{
for (const auto& addr : addresses) // v4
{
if (addr->transportStyle == dotnet::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;
}
}
if (!v4only)
{
for (const auto& addr : addresses) // v6
{
if (addr->transportStyle == dotnet::data::RouterInfo::eTransportSSU &&
addr->host.is_v6 ())
{
// insert NTCP address with host/port from SSU
m_RouterInfo.AddNTCPAddress (addr->host.to_string ().c_str (), addr->port);
break;
}
}
}
}
else
{
for (auto it = addresses.begin (); it != addresses.end ();)
{
if ((*it)->transportStyle == dotnet::data::RouterInfo::eTransportNTCP && !(*it)->IsNTCP2 () &&
(!v4only || (*it)->host.is_v4 ()))
{
it = addresses.erase (it);
if (v4only) break; // otherwise might be more than one address
}
else
++it;
}
}
}
void RouterContext::SetUnreachable ()
{
// set caps
uint8_t caps = m_RouterInfo.GetCaps ();
caps &= ~dotnet::data::RouterInfo::eReachable;
caps |= dotnet::data::RouterInfo::eUnreachable;
caps &= ~dotnet::data::RouterInfo::eFloodfill; // can't be floodfill
caps &= ~dotnet::data::RouterInfo::eSSUIntroducer; // can't be introducer
m_RouterInfo.SetCaps (caps);
// remove NTCP v4 address
PublishNTCPAddress (false);
// delete previous introducers
auto& addresses = m_RouterInfo.GetAddresses ();
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 &= ~dotnet::data::RouterInfo::eUnreachable;
caps |= dotnet::data::RouterInfo::eReachable;
caps |= dotnet::data::RouterInfo::eSSUIntroducer;
if (m_IsFloodfill)
caps |= dotnet::data::RouterInfo::eFloodfill;
m_RouterInfo.SetCaps (caps);
// insert NTCP back
bool ntcp; dotnet::config::GetOption("ntcp", ntcp);
if (ntcp)
PublishNTCPAddress (true);
// delete previous introducers
auto& addresses = m_RouterInfo.GetAddresses ();
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 == dotnet::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 = dotnet::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::UpdateNTCP2V6Address (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->IsPublishedNTCP2 ())
{
if (addr->host.is_v6 ())
{
if (addr->host != host)
{
addr->host = host;
updated = true;
}
found = true;
break;
}
else
port = addr->port; // NTCP2 v4
}
}
if (!found && port) // we have found NTCP2 v4 but not v6
{
m_RouterInfo.AddNTCP2Address (m_NTCP2Keys->staticPublicKey, m_NTCP2Keys->iv, host, port);
updated = true;
}
if (updated)
UpdateRouterInfo ();
}
void RouterContext::UpdateStats ()
{
if (m_IsFloodfill)
{
// update routers and leasesets
m_RouterInfo.SetProperty (dotnet::data::ROUTER_INFO_PROPERTY_LEASESETS, std::to_string(dotnet::data::netdb.GetNumLeaseSets ()));
m_RouterInfo.SetProperty (dotnet::data::ROUTER_INFO_PROPERTY_ROUTERS, std::to_string(dotnet::data::netdb.GetNumRouters ()));
UpdateRouterInfo ();
}
}
void RouterContext::UpdateTimestamp (uint64_t ts)
{
if (ts > m_LastUpdateTime + ROUTER_INFO_UPDATE_INTERVAL)
UpdateRouterInfo ();
}
bool RouterContext::Load ()
{
std::ifstream fk (dotnet::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 (dotnet::data::Keys)) // old keys file format
{
dotnet::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;
}
// read NTCP2 keys if available
std::ifstream n2k (dotnet::fs::DataDirPath (NTCP2_KEYS), std::ifstream::in | std::ifstream::binary);
if (n2k)
{
n2k.seekg (0, std::ios::end);
len = n2k.tellg();
n2k.seekg (0, std::ios::beg);
if (len == sizeof (NTCP2PrivateKeys))
{
m_NTCP2Keys.reset (new NTCP2PrivateKeys ());
n2k.read ((char *)m_NTCP2Keys.get (), sizeof (NTCP2PrivateKeys));
}
n2k.close ();
}
// read RouterInfo
m_RouterInfo.SetRouterIdentity (GetIdentity ());
dotnet::data::RouterInfo routerInfo(dotnet::fs::DataDirPath (ROUTER_INFO));
if (!routerInfo.IsUnreachable ()) // router.info looks good
{
m_RouterInfo.Update (routerInfo.GetBuffer (), routerInfo.GetBufferLen ());
m_RouterInfo.SetProperty ("coreVersion", DOTNET_VERSION);
m_RouterInfo.SetProperty ("router.version", DOTNET_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
// read NTCP2
bool ntcp2; dotnet::config::GetOption("ntcp2.enabled", ntcp2);
if (ntcp2)
{
if (!m_NTCP2Keys) NewNTCP2Keys ();
UpdateNTCP2Address (true); // enable NTCP2
}
else
UpdateNTCP2Address (false); // disable NTCP2
return true;
}
void RouterContext::SaveKeys ()
{
// save in the same format as .dat files
std::ofstream fk (dotnet::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<dotnet::tunnel::TunnelPool> RouterContext::GetTunnelPool () const
{
return dotnet::tunnel::tunnels.GetExploratoryPool ();
}
void RouterContext::HandleDNNPMessage (const uint8_t * buf, size_t len, std::shared_ptr<dotnet::tunnel::InboundTunnel> from)
{
dotnet::HandleDNNPMessage (CreateDNNPMessage (buf, GetDNNPMessageLength (buf, len), from));
}
void RouterContext::ProcessGarlicMessage (std::shared_ptr<DNNPMessage> msg)
{
std::unique_lock<std::mutex> l(m_GarlicMutex);
dotnet::garlic::GarlicDestination::ProcessGarlicMessage (msg);
}
void RouterContext::ProcessDeliveryStatusMessage (std::shared_ptr<DNNPMessage> msg)
{
std::unique_lock<std::mutex> l(m_GarlicMutex);
dotnet::garlic::GarlicDestination::ProcessDeliveryStatusMessage (msg);
}
void RouterContext::CleanupDestination ()
{
std::unique_lock<std::mutex> l(m_GarlicMutex);
dotnet::garlic::GarlicDestination::CleanupExpiredTags ();
}
uint32_t RouterContext::GetUptime () const
{
return dotnet::util::GetSecondsSinceEpoch () - m_StartupTime;
}
bool RouterContext::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) const
{
return m_Decryptor ? m_Decryptor->Decrypt (encrypted, data, ctx, true) : false;
}
bool RouterContext::DecryptTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) const
{
return m_Decryptor ? m_Decryptor->Decrypt (encrypted, data, ctx, false) : false;
}
dotnet::crypto::X25519Keys& RouterContext::GetStaticKeys ()
{
if (!m_StaticKeys)
{
if (!m_NTCP2Keys) NewNTCP2Keys ();
auto x = new dotnet::crypto::X25519Keys (m_NTCP2Keys->staticPrivateKey, m_NTCP2Keys->staticPublicKey);
if (!m_StaticKeys)
m_StaticKeys.reset (x);
else
delete x;
}
return *m_StaticKeys;
}
}

155
libdotnet/RouterContext.h Normal file
View file

@ -0,0 +1,155 @@
#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 dotnet
{
const char ROUTER_INFO[] = "router.info";
const char ROUTER_KEYS[] = "router.keys";
const char NTCP2_KEYS[] = "ntcp2.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 dotnet::garlic::GarlicDestination
{
private:
struct NTCP2PrivateKeys
{
uint8_t staticPublicKey[32];
uint8_t staticPrivateKey[32];
uint8_t iv[16];
};
public:
RouterContext ();
void Init ();
const dotnet::data::PrivateKeys& GetPrivateKeys () const { return m_Keys; };
dotnet::data::RouterInfo& GetRouterInfo () { return m_RouterInfo; };
std::shared_ptr<const dotnet::data::RouterInfo> GetSharedRouterInfo () const
{
return std::shared_ptr<const dotnet::data::RouterInfo> (&m_RouterInfo,
[](const dotnet::data::RouterInfo *) {});
}
std::shared_ptr<dotnet::garlic::GarlicDestination> GetSharedDestination ()
{
return std::shared_ptr<dotnet::garlic::GarlicDestination> (this,
[](dotnet::garlic::GarlicDestination *) {});
}
const uint8_t * GetNTCP2StaticPublicKey () const { return m_NTCP2Keys ? m_NTCP2Keys->staticPublicKey : nullptr; };
const uint8_t * GetNTCP2StaticPrivateKey () const { return m_NTCP2Keys ? m_NTCP2Keys->staticPrivateKey : nullptr; };
const uint8_t * GetNTCP2IV () const { return m_NTCP2Keys ? m_NTCP2Keys->iv : nullptr; };
dotnet::crypto::X25519Keys& GetStaticKeys ();
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; };
uint64_t GetTransitBandwidthLimit () const { return (m_BandwidthLimit*m_ShareRatio)/100LL; };
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; };
bool DecryptTunnelBuildRecord (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) const;
void UpdatePort (int port); // called from Daemon
void UpdateAddress (const boost::asio::ip::address& host); // called from SSU or Daemon
void PublishNTCP2Address (int port, bool publish = true);
void UpdateNTCP2Address (bool enable);
void PublishNTCPAddress (bool publish, bool v4only = true);
bool AddIntroducer (const dotnet::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 */
void SetShareRatio (int percents); // 0 - 100
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 UpdateNTCP2V6Address (const boost::asio::ip::address& host); // called from NTCP2 session
void UpdateStats ();
void UpdateTimestamp (uint64_t ts); // in seconds, called from NetDb before publishing
void CleanupDestination (); // garlic destination
// implements LocalDestination
std::shared_ptr<const dotnet::data::IdentityEx> GetIdentity () const { return m_Keys.GetPublic (); };
bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) const;
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 dotnet::data::LocalLeaseSet> GetLeaseSet () { return nullptr; };
std::shared_ptr<dotnet::tunnel::TunnelPool> GetTunnelPool () const;
void HandleDNNPMessage (const uint8_t * buf, size_t len, std::shared_ptr<dotnet::tunnel::InboundTunnel> from);
// override GarlicDestination
void ProcessGarlicMessage (std::shared_ptr<DNNPMessage> msg);
void ProcessDeliveryStatusMessage (std::shared_ptr<DNNPMessage> msg);
private:
void CreateNewRouter ();
void NewRouterInfo ();
void UpdateRouterInfo ();
void NewNTCP2Keys ();
bool Load ();
void SaveKeys ();
private:
dotnet::data::RouterInfo m_RouterInfo;
dotnet::data::PrivateKeys m_Keys;
std::shared_ptr<dotnet::crypto::CryptoKeyDecryptor> m_Decryptor;
uint64_t m_LastUpdateTime; // in seconds
bool m_AcceptsTunnels, m_IsFloodfill;
uint64_t m_StartupTime; // in seconds since epoch
uint64_t m_BandwidthLimit; // allowed bandwidth
int m_ShareRatio;
RouterStatus m_Status;
RouterError m_Error;
int m_NetID;
std::mutex m_GarlicMutex;
std::unique_ptr<NTCP2PrivateKeys> m_NTCP2Keys;
std::unique_ptr<dotnet::crypto::X25519Keys> m_StaticKeys;
};
extern RouterContext context;
}
#endif

921
libdotnet/RouterInfo.cpp Normal file
View file

@ -0,0 +1,921 @@
#include <stdio.h>
#include <string.h>
#include "DotNetEndian.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.hpp"
#include "RouterContext.h"
#include "RouterInfo.h"
namespace dotnet
{
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 identity 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 = dotnet::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)
{
// reject RSA signatures
if (m_RouterIdentity->IsRSA ())
{
LogPrint (eLogError, "RouterInfo: RSA signature type is not allowed");
m_IsUnreachable = true;
return;
}
// 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;
auto address = std::make_shared<Address>();
s.read ((char *)&address->cost, sizeof (address->cost));
s.read ((char *)&address->date, sizeof (address->date));
bool isNTCP2Only = false;
char transportStyle[6];
auto transportStyleLen = ReadString (transportStyle, 6, s) - 1;
if (!strncmp (transportStyle, "NTCP", 4)) // NTCP or NTCP2
{
address->transportStyle = eTransportNTCP;
if (transportStyleLen > 4 && transportStyle[4] == '2') isNTCP2Only= true;
}
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 BOOST_VERSION >= 104900
if (!address->host.is_unspecified ()) // check if address is valid
#else
address->host.to_string (ecode);
if (!ecode)
#endif
{
// 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 (!strcmp (key, "s")) // ntcp2 static key
{
if (!address->ntcp2) address->ntcp2.reset (new NTCP2Ext ());
supportedTransports |= (address->host.is_v4 ()) ? eNTCP2V4 : eNTCP2V6;
Base64ToByteStream (value, strlen (value), address->ntcp2->staticKey, 32);
}
else if (!strcmp (key, "i")) // ntcp2 iv
{
if (!address->ntcp2) address->ntcp2.reset (new NTCP2Ext ());
supportedTransports |= (address->host.is_v4 ()) ? eNTCP2V4 : eNTCP2V6;
Base64ToByteStream (value, strlen (value), address->ntcp2->iv, 16);
address->ntcp2->isPublished = true; // presence if "i" means "published"
}
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);
else if (!strcmp (key, "iexp"))
introducer.iExp = boost::lexical_cast<uint32_t>(value);
}
if (!s) return;
}
if (introducers) supportedTransports |= eSSUV4; // in case if host is not presented
if (isNTCP2Only && address->ntcp2) address->ntcp2->isNTCP2Only = true;
if (supportedTransports)
{
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) != dotnet::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 | Caps::eHighBandwidth;
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'
else
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' */
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 (address.IsNTCP2 () ? "NTCP2" : "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);
if (!address.IsNTCP2 () || address.IsPublishedNTCP2 ())
{
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++;
}
i = 0;
for (const auto& introducer: address.ssu->introducers)
{
if (introducer.iExp) // expiration is specified
{
WriteString ("iexp" + boost::lexical_cast<std::string>(i), properties);
properties << '=';
WriteString (boost::lexical_cast<std::string>(introducer.iExp), 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 << ';';
}
}
if (address.IsPublishedNTCP2 ())
{
// publish i for NTCP2
WriteString ("i", properties); properties << '=';
WriteString (address.ntcp2->iv.ToBase64 (), properties); properties << ';';
}
if (!address.IsNTCP2 () || address.IsPublishedNTCP2 ())
{
WriteString ("port", properties);
properties << '=';
WriteString (boost::lexical_cast<std::string>(address.port), properties);
properties << ';';
}
if (address.IsNTCP2 ())
{
// publish s and v for NTCP2
WriteString ("s", properties); properties << '=';
WriteString (address.ntcp2->staticKey.ToBase64 (), properties); properties << ';';
WriteString ("v", properties); properties << '=';
WriteString ("2", 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 = dotnet::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 = 6;
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_front(std::move(addr)); // always make NTCP first
}
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;
}
void RouterInfo::AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv, const boost::asio::ip::address& host, int port)
{
auto addr = std::make_shared<Address>();
addr->host = host;
addr->port = port;
addr->transportStyle = eTransportNTCP;
addr->cost = port ? 3 : 14; // override from RouterContext::PublishNTCP2Address
addr->date = 0;
addr->ntcp2.reset (new NTCP2Ext ());
addr->ntcp2->isNTCP2Only = true; // NTCP2 only address
if (port) addr->ntcp2->isPublished = true;
memcpy (addr->ntcp2->staticKey, staticKey, 32);
memcpy (addr->ntcp2->iv, iv, 16);
m_Addresses->push_back(std::move(addr));
}
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::IsNTCP2 (bool v4only) const
{
if (v4only)
return m_SupportedTransports & eNTCP2V4;
else
return m_SupportedTransports & (eNTCP2V4 | eNTCP2V6);
}
bool RouterInfo::IsV6 () const
{
return m_SupportedTransports & (eNTCPV6 | eSSUV6);
}
bool RouterInfo::IsV4 () const
{
return m_SupportedTransports & (eNTCPV4 | eSSUV4 | eNTCP2V4);
}
void RouterInfo::EnableV6 ()
{
if (!IsV6 ())
m_SupportedTransports |= eNTCPV6 | eSSUV6 | eNTCP2V6;
}
void RouterInfo::EnableV4 ()
{
if (!IsV4 ())
m_SupportedTransports |= eNTCPV4 | eSSUV4 | eNTCP2V4;
}
void RouterInfo::DisableV6 ()
{
if (IsV6 ())
{
m_SupportedTransports &= ~(eNTCPV6 | eSSUV6 | eNTCP2V6);
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 | eNTCP2V4);
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 (
[v4only](std::shared_ptr<const RouterInfo::Address> address)->bool
{
return (address->transportStyle == eTransportNTCP) && !address->IsNTCP2Only () && (!v4only || address->host.is_v4 ());
});
}
std::shared_ptr<const RouterInfo::Address> RouterInfo::GetSSUAddress (bool v4only) const
{
return GetAddress (
[v4only](std::shared_ptr<const RouterInfo::Address> address)->bool
{
return (address->transportStyle == eTransportSSU) && (!v4only || address->host.is_v4 ());
});
}
std::shared_ptr<const RouterInfo::Address> RouterInfo::GetSSUV6Address () const
{
return GetAddress (
[](std::shared_ptr<const RouterInfo::Address> address)->bool
{
return (address->transportStyle == eTransportSSU) && address->host.is_v6 ();
});
}
template<typename Filter>
std::shared_ptr<const RouterInfo::Address> RouterInfo::GetAddress (Filter filter) const
{
// TODO: make it more generic using comparator
#if (BOOST_VERSION >= 105300)
auto addresses = boost::atomic_load (&m_Addresses);
#else
auto addresses = m_Addresses;
#endif
for (const auto& address : *addresses)
if (filter (address)) return address;
return nullptr;
}
std::shared_ptr<const RouterInfo::Address> RouterInfo::GetNTCP2Address (bool publishedOnly, bool v4only) const
{
return GetAddress (
[publishedOnly, v4only](std::shared_ptr<const RouterInfo::Address> address)->bool
{
return address->IsNTCP2 () && (!publishedOnly || address->IsPublishedNTCP2 ()) && (!v4only || address->host.is_v4 ());
});
}
std::shared_ptr<RouterProfile> RouterInfo::GetProfile () const
{
if (!m_Profile)
m_Profile = GetRouterProfile (GetIdentHash ());
return m_Profile;
}
void RouterInfo::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const
{
auto encryptor = m_RouterIdentity->CreateEncryptor (nullptr);
if (encryptor)
encryptor->Encrypt (data, encrypted, ctx, true);
}
}
}

242
libdotnet/RouterInfo.h Normal file
View file

@ -0,0 +1,242 @@
#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 dotnet
{
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,
eNTCP2V4 = 0x10,
eNTCP2V6 = 0x20
};
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
{
Introducer (): iExp (0) {};
boost::asio::ip::address iHost;
int iPort;
IntroKey iKey;
uint32_t iTag;
uint32_t iExp;
};
struct SSUExt
{
int mtu;
IntroKey key; // intro key for SSU
std::vector<Introducer> introducers;
};
struct NTCP2Ext
{
Tag<32> staticKey;
Tag<16> iv;
bool isPublished = false;
bool isNTCP2Only = false;
};
struct Address
{
TransportStyle transportStyle;
boost::asio::ip::address host;
int port;
uint64_t date;
uint8_t cost;
std::unique_ptr<SSUExt> ssu; // not null for SSU
std::unique_ptr<NTCP2Ext> ntcp2; // not null for NTCP2
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 && IsNTCP2 () == other.IsNTCP2 () &&
host == other.host && port == other.port;
}
bool operator!=(const Address& other) const
{
return !(*this == other);
}
bool IsNTCP2 () const { return (bool)ntcp2; };
bool IsPublishedNTCP2 () const { return IsNTCP2 () && ntcp2->isPublished; };
bool IsNTCP2Only () const { return ntcp2 && ntcp2->isNTCP2Only; };
};
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> GetNTCP2Address (bool publishedOnly, 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);
void AddNTCP2Address (const uint8_t * staticKey, const uint8_t * iv, const boost::asio::ip::address& host = boost::asio::ip::address(), int port = 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 IsNTCP2 (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 HasValidAddresses () const { return 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
std::shared_ptr<const IdentityEx> GetIdentity () const { return m_RouterIdentity; };
void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const;
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);
template<typename Filter>
std::shared_ptr<const Address> GetAddress (Filter filter) 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

798
libdotnet/SSU.cpp Normal file
View file

@ -0,0 +1,798 @@
#include <string.h>
#include <boost/bind.hpp>
#include "Log.h"
#include "Timestamp.h"
#include "RouterContext.h"
#include "NetDb.hpp"
#include "SSU.h"
namespace dotnet
{
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 ();
session = nullptr;
}
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 dotnet::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 dotnet::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 ", dotnet::data::GetIdentHashAbbreviation (router->GetIdentHash ()), " doesn't have SSU address");
}
void SSUServer::CreateSession (std::shared_ptr<const dotnet::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 dotnet::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 [", dotnet::data::GetIdentHashAbbreviation (router->GetIdentHash ()), "] ",
remoteEndpoint.address ().to_string (), ":", remoteEndpoint.port ());
session->Connect ();
}
}
void SSUServer::CreateSessionThroughIntroducer (std::shared_ptr<const dotnet::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 is presented already
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)
{
uint32_t ts = dotnet::util::GetSecondsSinceEpoch ();
std::shared_ptr<SSUSession> introducerSession;
const dotnet::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]);
if (intr->iExp > 0 && ts > intr->iExp) continue; // skip expired introducer
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 non-expired introducers presented");
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;
}
#if BOOST_VERSION >= 104900
if (!address->host.is_unspecified () && address->port)
#endif
{
// create session
auto session = std::make_shared<SSUSession> (*this, remoteEndpoint, router, peerTest);
m_Sessions[remoteEndpoint] = session;
// introduce
LogPrint (eLogInfo, "SSU: Introduce new session to [", dotnet::data::GetIdentHashAbbreviation (router->GetIdentHash ()),
"] through introducer ", introducer->iHost, ":", introducer->iPort);
session->WaitForIntroduction ();
if (dotnet::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 ", dotnet::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 = dotnet::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 (dotnet::context.GetStatus () == eRouterStatusTesting)
{
// we still don't know if we need introducers
ScheduleIntroducersUpdateTimer ();
return;
}
if (dotnet::context.GetStatus () == eRouterStatusOK) return; // we don't need introducers anymore
// we are firewalled
if (!dotnet::context.IsUnreachable ()) dotnet::context.SetUnreachable ();
std::list<boost::asio::ip::udp::endpoint> newList;
size_t numIntroducers = 0;
uint32_t ts = dotnet::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
dotnet::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 ();
dotnet::data::RouterInfo::Introducer introducer;
introducer.iHost = ep.address ();
introducer.iPort = ep.port ();
introducer.iTag = it1->GetRelayTag ();
introducer.iKey = it1->GetIntroKey ();
if (dotnet::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 = dotnet::data::netdb.GetRandomIntroducer ();
if (introducer)
CreateSession (introducer);
}
ScheduleIntroducersUpdateTimer ();
}
}
void SSUServer::NewPeerTest (uint32_t nonce, PeerTestParticipant role, std::shared_ptr<SSUSession> session)
{
m_PeerTests[nonce] = { dotnet::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 = dotnet::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 = dotnet::util::GetSecondsSinceEpoch ();
for (auto& it: m_Sessions)
if (it.second->IsTerminationTimeoutExpired (ts))
{
auto session = it.second;
if (it.first != session->GetRemoteEndpoint ())
LogPrint (eLogWarning, "SSU: remote endpoint ", session->GetRemoteEndpoint (), " doesn't match key ", it.first, " adjusted");
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 = dotnet::util::GetSecondsSinceEpoch ();
for (auto& it: m_SessionsV6)
if (it.second->IsTerminationTimeoutExpired (ts))
{
auto session = it.second;
if (it.first != session->GetRemoteEndpoint ())
LogPrint (eLogWarning, "SSU: remote endpoint ", session->GetRemoteEndpoint (), " doesn't match key ", it.first);
m_ServiceV6.post ([session]
{
LogPrint (eLogWarning, "SSU: no activity with ", session->GetRemoteEndpoint (), " for ", session->GetTerminationTimeout (), " seconds");
session->Failed ();
});
}
ScheduleTerminationV6 ();
}
}
}
}

138
libdotnet/SSU.h Normal file
View 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 "DotNetEndian.h"
#include "Identity.h"
#include "RouterInfo.h"
#include "DNNPProtocol.h"
#include "SSUSession.h"
namespace dotnet
{
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
{
dotnet::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 dotnet::data::RouterInfo> router, bool peerTest = false, bool v4only = false);
void CreateSession (std::shared_ptr<const dotnet::data::RouterInfo> router,
const boost::asio::ip::address& addr, int port, bool peerTest = false);
void CreateDirectSession (std::shared_ptr<const dotnet::data::RouterInfo> router, boost::asio::ip::udp::endpoint remoteEndpoint, bool peerTest);
std::shared_ptr<SSUSession> FindSession (std::shared_ptr<const dotnet::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 dotnet::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
libdotnet/SSUData.cpp Normal file
View file

@ -0,0 +1,513 @@
#include <stdlib.h>
#include <boost/bind.hpp>
#include "Log.h"
#include "Timestamp.h"
#include "NetDb.hpp"
#include "SSU.h"
#include "SSUData.h"
#ifdef WITH_EVENTS
#include "Event.h"
#endif
namespace dotnet
{
namespace transport
{
void IncompleteMessage::AttachNextFragment (const uint8_t * fragment, size_t fragmentSize)
{
if (msg->len + fragmentSize > msg->maxLen)
{
LogPrint (eLogWarning, "SSU: DNNP message size ", msg->maxLen, " is not enough");
auto newMsg = NewDNNPMessage ();
*newMsg = *msg;
msg = newMsg;
}
if (msg->Concat (fragment, fragmentSize) < fragmentSize)
LogPrint (eLogError, "SSU: DNNP 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 dotnet::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 dotnet::data::IdentHash& remoteIdent)
{
auto routerInfo = dotnet::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 = NewDNNPShortMessage ();
msg->len -= DNNP_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 = dotnet::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 = dotnet::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 () == eDNNPDeliveryStatus)
{
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<dotnet::DNNPMessage> 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 = dotnet::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 && fragmentNum <= 127)
{
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) || fragmentNum == 127; // 127 fragments max
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 = dotnet::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 = dotnet::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 ||
dotnet::util::GetSecondsSinceEpoch () > m_LastMessageReceivedTime + DECAY_INTERVAL)
m_ReceivedMessages.clear ();
ScheduleIncompleteMessagesCleanup ();
}
}
}
}

131
libdotnet/SSUData.h Normal file
View 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 "DNNPProtocol.h"
#include "Identity.h"
#include "RouterInfo.h"
namespace dotnet
{
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<DNNPMessage> msg;
int nextFragmentNum;
uint32_t lastFragmentInsertTime; // in seconds
std::set<std::unique_ptr<Fragment>, FragmentCmp> savedFragments;
IncompleteMessage (std::shared_ptr<DNNPMessage> 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<dotnet::DNNPMessage> msg);
void AdjustPacketSize (std::shared_ptr<const dotnet::data::RouterInfo> remoteRouter);
void UpdatePacketSize (const dotnet::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;
dotnet::DNNPMessagesHandler m_Handler;
uint32_t m_LastMessageReceivedTime; // in second
};
}
}
#endif

1205
libdotnet/SSUSession.cpp Normal file

File diff suppressed because it is too large Load diff

167
libdotnet/SSUSession.h Normal file
View file

@ -0,0 +1,167 @@
#ifndef SSU_SESSION_H__
#define SSU_SESSION_H__
#include <inttypes.h>
#include <set>
#include <memory>
#include "Crypto.h"
#include "DNNPProtocol.h"
#include "TransportSession.h"
#include "SSUData.h"
namespace dotnet
{
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
const size_t SSU_MAX_DNNP_MESSAGE_SIZE = 32768;
// 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 dotnet::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 dotnet::data::RouterInfo::Introducer& introducer,
std::shared_ptr<const dotnet::data::RouterInfo> to); // Alice to Charlie
void WaitForIntroduction ();
void Close ();
void Done ();
void Failed ();
const boost::asio::ip::udp::endpoint& GetRemoteEndpoint () { return m_RemoteEndpoint; };
bool IsV6 () const { return m_RemoteEndpoint.address ().is_v6 (); };
void SendDNNPMessages (const std::vector<std::shared_ptr<DNNPMessage> >& 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 dotnet::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 PostDNNPMessages (std::vector<std::shared_ptr<DNNPMessage> > 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);
void SendSessionRequest ();
void SendRelayRequest (const dotnet::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 dotnet::crypto::AESKey& aesKey,
const uint8_t * iv, const dotnet::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 dotnet::crypto::AESKey& aesKey);
void DecryptSessionKey (uint8_t * buf, size_t len);
bool Validate (uint8_t * buf, size_t len, const dotnet::crypto::MACKey& macKey);
void Reset ();
private:
friend class SSUData; // TODO: change in later
SSUServer& m_Server;
const 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
dotnet::crypto::CBCEncryption m_SessionKeyEncryption;
dotnet::crypto::CBCDecryption m_SessionKeyDecryption;
dotnet::crypto::AESKey m_SessionKey;
dotnet::crypto::MACKey m_MacKey;
dotnet::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 dotnet::data::RouterInfo> > m_RelayRequests; // nonce->Charlie
};
}
}
#endif

139
libdotnet/Signature.cpp Normal file
View file

@ -0,0 +1,139 @@
#include <memory>
#include "Log.h"
#include "Signature.h"
namespace dotnet
{
namespace crypto
{
#if OPENSSL_EDDSA
EDDSA25519Verifier::EDDSA25519Verifier ():
m_Pkey (nullptr)
{
m_MDCtx = EVP_MD_CTX_create ();
}
EDDSA25519Verifier::~EDDSA25519Verifier ()
{
EVP_MD_CTX_destroy (m_MDCtx);
if (m_Pkey) EVP_PKEY_free (m_Pkey);
}
void EDDSA25519Verifier::SetPublicKey (const uint8_t * signingKey)
{
m_Pkey = EVP_PKEY_new_raw_public_key (EVP_PKEY_ED25519, NULL, signingKey, 32);
EVP_DigestVerifyInit (m_MDCtx, NULL, NULL, NULL, m_Pkey);
}
bool EDDSA25519Verifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const
{
return EVP_DigestVerify (m_MDCtx, signature, 64, buf, len);
}
#else
EDDSA25519Verifier::EDDSA25519Verifier ()
{
}
EDDSA25519Verifier::~EDDSA25519Verifier ()
{
}
void EDDSA25519Verifier::SetPublicKey (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);
}
#endif
EDDSA25519SignerCompat::EDDSA25519SignerCompat (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey)
{
// expand key
Ed25519::ExpandPrivateKey (signingPrivateKey, m_ExpandedPrivateKey);
// 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);
}
EDDSA25519SignerCompat::~EDDSA25519SignerCompat ()
{
}
void EDDSA25519SignerCompat::Sign (const uint8_t * buf, int len, uint8_t * signature) const
{
GetEd25519 ()->Sign (m_ExpandedPrivateKey, m_PublicKeyEncoded, buf, len, signature);
}
#if OPENSSL_EDDSA
EDDSA25519Signer::EDDSA25519Signer (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey):
m_Fallback (nullptr)
{
m_Pkey = EVP_PKEY_new_raw_private_key (EVP_PKEY_ED25519, NULL, signingPrivateKey, 32);
uint8_t publicKey[EDDSA25519_PUBLIC_KEY_LENGTH];
size_t len = EDDSA25519_PUBLIC_KEY_LENGTH;
EVP_PKEY_get_raw_public_key (m_Pkey, publicKey, &len);
if (signingPublicKey && memcmp (publicKey, signingPublicKey, EDDSA25519_PUBLIC_KEY_LENGTH))
{
LogPrint (eLogWarning, "EdDSA public key mismatch. Fallback");
EVP_PKEY_free (m_Pkey);
m_Fallback = new EDDSA25519SignerCompat (signingPrivateKey, signingPublicKey);
}
else
{
m_MDCtx = EVP_MD_CTX_create ();
EVP_DigestSignInit (m_MDCtx, NULL, NULL, NULL, m_Pkey);
}
}
EDDSA25519Signer::~EDDSA25519Signer ()
{
if (m_Fallback) delete m_Fallback;
else
{
EVP_MD_CTX_destroy (m_MDCtx);
EVP_PKEY_free (m_Pkey);
}
}
void EDDSA25519Signer::Sign (const uint8_t * buf, int len, uint8_t * signature) const
{
if (m_Fallback) return m_Fallback->Sign (buf, len, signature);
else
{
size_t l = 64;
uint8_t sig[64]; // temporary buffer for signature. openssl issue #7232
EVP_DigestSign (m_MDCtx, sig, &l, buf, len);
memcpy (signature, sig, 64);
}
}
#endif
}
}

530
libdotnet/Signature.h Normal file
View file

@ -0,0 +1,530 @@
#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/evp.h>
#include "Crypto.h"
#include "Ed25519.h"
#include "Gost.h"
namespace dotnet
{
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; };
virtual void SetPublicKey (const uint8_t * signingKey) = 0;
};
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 ()
{
m_PublicKey = CreateDSA ();
}
void SetPublicKey (const uint8_t * signingKey)
{
DSA_set0_key (m_PublicKey, BN_bin2bn (signingKey, DSA_PUBLIC_KEY_LENGTH, NULL), NULL);
}
~DSAVerifier ()
{
DSA_free (m_PublicKey);
}
bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const
{
// calculate SHA1 digest
uint8_t digest[20];
SHA1 (buf, len, digest);
// signature
DSA_SIG * sig = DSA_SIG_new();
DSA_SIG_set0 (sig, BN_bin2bn (signature, DSA_SIGNATURE_LENGTH/2, NULL), BN_bin2bn (signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2, NULL));
// DSA verification
int ret = DSA_do_verify (digest, 20, sig, m_PublicKey);
DSA_SIG_free(sig);
return ret;
}
size_t GetPublicKeyLen () const { return DSA_PUBLIC_KEY_LENGTH; };
size_t GetSignatureLen () const { return DSA_SIGNATURE_LENGTH; };
private:
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 };
};
// EcDSA
template<typename Hash, int curve, size_t keyLen>
class ECDSAVerifier: public Verifier
{
public:
ECDSAVerifier ()
{
m_PublicKey = EC_KEY_new_by_curve_name (curve);
}
void SetPublicKey (const uint8_t * signingKey)
{
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);
}
// EdDSA
class EDDSA25519Verifier: public Verifier
{
public:
EDDSA25519Verifier ();
void SetPublicKey (const uint8_t * signingKey);
~EDDSA25519Verifier ();
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:
#if OPENSSL_EDDSA
EVP_PKEY * m_Pkey;
EVP_MD_CTX * m_MDCtx;
#else
EDDSAPoint m_PublicKey;
uint8_t m_PublicKeyEncoded[EDDSA25519_PUBLIC_KEY_LENGTH];
#endif
};
class EDDSA25519SignerCompat: public Signer
{
public:
EDDSA25519SignerCompat (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey = nullptr);
// we pass signingPublicKey to check if it matches private key
~EDDSA25519SignerCompat ();
void Sign (const uint8_t * buf, int len, uint8_t * signature) const;
const uint8_t * GetPublicKey () const { return m_PublicKeyEncoded; }; // for keys creation
private:
uint8_t m_ExpandedPrivateKey[64];
uint8_t m_PublicKeyEncoded[EDDSA25519_PUBLIC_KEY_LENGTH];
};
#if OPENSSL_EDDSA
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
~EDDSA25519Signer ();
void Sign (const uint8_t * buf, int len, uint8_t * signature) const;
private:
EVP_PKEY * m_Pkey;
EVP_MD_CTX * m_MDCtx;
EDDSA25519SignerCompat * m_Fallback;
};
#else
typedef EDDSA25519SignerCompat EDDSA25519Signer;
#endif
inline void CreateEDDSA25519RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
{
#if OPENSSL_EDDSA
EVP_PKEY *pkey = NULL;
EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id (EVP_PKEY_ED25519, NULL);
EVP_PKEY_keygen_init (pctx);
EVP_PKEY_keygen (pctx, &pkey);
EVP_PKEY_CTX_free (pctx);
size_t len = EDDSA25519_PUBLIC_KEY_LENGTH;
EVP_PKEY_get_raw_public_key (pkey, signingPublicKey, &len);
len = EDDSA25519_PRIVATE_KEY_LENGTH;
EVP_PKEY_get_raw_private_key (pkey, signingPrivateKey, &len);
EVP_PKEY_free (pkey);
#else
RAND_bytes (signingPrivateKey, EDDSA25519_PRIVATE_KEY_LENGTH);
EDDSA25519Signer signer (signingPrivateKey);
memcpy (signingPublicKey, signer.GetPublicKey (), EDDSA25519_PUBLIC_KEY_LENGTH);
#endif
}
// ГОСТ Р 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):
m_ParamSet (paramSet), m_PublicKey (nullptr)
{
}
void SetPublicKey (const uint8_t * signingKey)
{
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 ()
{
if (m_PublicKey) 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;
// RedDSA
typedef EDDSA25519Verifier RedDSA25519Verifier;
class RedDSA25519Signer: public Signer
{
public:
RedDSA25519Signer (const uint8_t * signingPrivateKey)
{
memcpy (m_PrivateKey, signingPrivateKey, EDDSA25519_PRIVATE_KEY_LENGTH);
BN_CTX * ctx = BN_CTX_new ();
auto publicKey = GetEd25519 ()->GeneratePublicKey (m_PrivateKey, ctx);
GetEd25519 ()->EncodePublicKey (publicKey, m_PublicKeyEncoded, ctx);
BN_CTX_free (ctx);
}
~RedDSA25519Signer () {};
void Sign (const uint8_t * buf, int len, uint8_t * signature) const
{
GetEd25519 ()->SignRedDSA (m_PrivateKey, m_PublicKeyEncoded, buf, len, signature);
}
const uint8_t * GetPublicKey () const { return m_PublicKeyEncoded; }; // for keys creation
private:
uint8_t m_PrivateKey[EDDSA25519_PRIVATE_KEY_LENGTH];
uint8_t m_PublicKeyEncoded[EDDSA25519_PUBLIC_KEY_LENGTH];
};
inline void CreateRedDSA25519RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey)
{
GetEd25519 ()->CreateRedDSAPrivateKey (signingPrivateKey);
RedDSA25519Signer signer (signingPrivateKey);
memcpy (signingPublicKey, signer.GetPublicKey (), EDDSA25519_PUBLIC_KEY_LENGTH);
}
}
}
#endif

155
libdotnet/Siphash.h Normal file
View file

@ -0,0 +1,155 @@
/**
This code is licensed under the MCGSI Public License
Copyright 2018 Jeff Becker
Kovri go write your own code
*/
#ifndef SIPHASH_H
#define SIPHASH_H
#include <cstdint>
#include "Crypto.h"
#if !OPENSSL_SIPHASH
namespace dotnet
{
namespace crypto
{
namespace siphash
{
constexpr int crounds = 2;
constexpr int drounds = 4;
inline uint64_t rotl(const uint64_t & x, int b)
{
uint64_t ret = x << b;
ret |= x >> (64 - b);
return ret;
}
inline void u32to8le(const uint32_t & v, uint8_t * p)
{
p[0] = (uint8_t) v;
p[1] = (uint8_t) (v >> 8);
p[2] = (uint8_t) (v >> 16);
p[3] = (uint8_t) (v >> 24);
}
inline void u64to8le(const uint64_t & v, uint8_t * p)
{
p[0] = v & 0xff;
p[1] = (v >> 8) & 0xff;
p[2] = (v >> 16) & 0xff;
p[3] = (v >> 24) & 0xff;
p[4] = (v >> 32) & 0xff;
p[5] = (v >> 40) & 0xff;
p[6] = (v >> 48) & 0xff;
p[7] = (v >> 56) & 0xff;
}
inline uint64_t u8to64le(const uint8_t * p)
{
uint64_t i = 0;
int idx = 0;
while(idx < 8)
{
i |= ((uint64_t) p[idx]) << (idx * 8);
++idx;
}
return i;
}
inline void round(uint64_t & _v0, uint64_t & _v1, uint64_t & _v2, uint64_t & _v3)
{
_v0 += _v1;
_v1 = rotl(_v1, 13);
_v1 ^= _v0;
_v0 = rotl(_v0, 32);
_v2 += _v3;
_v3 = rotl(_v3, 16);
_v3 ^= _v2;
_v0 += _v3;
_v3 = rotl(_v3, 21);
_v3 ^= _v0;
_v2 += _v1;
_v1 = rotl(_v1, 17);
_v1 ^= _v2;
_v2 = rotl(_v2, 32);
}
}
/** hashsz must be 8 or 16 */
template<std::size_t hashsz>
inline void Siphash(uint8_t * h, const uint8_t * buf, std::size_t bufsz, const uint8_t * key)
{
uint64_t v0 = 0x736f6d6570736575ULL;
uint64_t v1 = 0x646f72616e646f6dULL;
uint64_t v2 = 0x6c7967656e657261ULL;
uint64_t v3 = 0x7465646279746573ULL;
const uint64_t k0 = siphash::u8to64le(key);
const uint64_t k1 = siphash::u8to64le(key + 8);
uint64_t msg;
int i;
const uint8_t * end = buf + bufsz - (bufsz % sizeof(uint64_t));
auto left = bufsz & 7;
uint64_t b = ((uint64_t)bufsz) << 56;
v3 ^= k1;
v2 ^= k0;
v1 ^= k1;
v0 ^= k0;
if(hashsz == 16) v1 ^= 0xee;
while(buf != end)
{
msg = siphash::u8to64le(buf);
v3 ^= msg;
for(i = 0; i < siphash::crounds; ++i)
siphash::round(v0, v1, v2, v3);
v0 ^= msg;
buf += 8;
}
while(left)
{
--left;
b |= ((uint64_t)(buf[left])) << (left * 8);
}
v3 ^= b;
for(i = 0; i < siphash::crounds; ++i)
siphash::round(v0, v1, v2, v3);
v0 ^= b;
if(hashsz == 16)
v2 ^= 0xee;
else
v2 ^= 0xff;
for(i = 0; i < siphash::drounds; ++i)
siphash::round(v0, v1, v2, v3);
b = v0 ^ v1 ^ v2 ^ v3;
siphash::u64to8le(b, h);
if(hashsz == 8) return;
v1 ^= 0xdd;
for (i = 0; i < siphash::drounds; ++i)
siphash::round(v0, v1, v2, v3);
b = v0 ^ v1 ^ v2 ^ v3;
siphash::u64to8le(b, h + 8);
}
}
}
#endif
#endif

1222
libdotnet/Streaming.cpp Normal file

File diff suppressed because it is too large Load diff

355
libdotnet/Streaming.h Normal file
View file

@ -0,0 +1,355 @@
#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 "DotNetEndian.h"
#include "Identity.h"
#include "LeaseSet.h"
#include "DNNPProtocol.h"
#include "Garlic.h"
#include "Tunnel.h"
#include "util.h" // MemoryPool
namespace dotnet
{
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 uint16_t PACKET_FLAG_OFFLINE_SIGNATURE = 0x0800;
const size_t STREAMING_MTU = 1730;
const size_t MAX_PACKET_SIZE = 4096;
const size_t COMPRESSION_THRESHOLD_SIZE = 66;
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 int SYN_TIMEOUT = 200; // how long we wait for SYN after follow-on, 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
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 dotnet::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 dotnet::data::LeaseSet> GetRemoteLeaseSet () const { return m_RemoteLeaseSet; };
std::shared_ptr<const dotnet::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 AsyncClose() { m_Service.post(std::bind(&Stream::Close, shared_from_this())); };
/** only call close from destination thread, use Stream::AsyncClose for other threads */
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);
bool ProcessOptions (uint16_t flags, 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 dotnet::data::IdentityEx> m_RemoteIdentity;
std::shared_ptr<const dotnet::crypto::Verifier> m_TransientVerifier; // in case of offline key
std::shared_ptr<const dotnet::data::LeaseSet> m_RemoteLeaseSet;
std::shared_ptr<dotnet::garlic::GarlicRoutingSession> m_RoutingSession;
std::shared_ptr<const dotnet::data::Lease> m_CurrentRemoteLease;
std::shared_ptr<dotnet::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, m_AckDelay;
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<dotnet::client::ClientDestination> owner, uint16_t localPort = 0, bool gzip = true);
~StreamingDestination ();
void Start ();
void Stop ();
std::shared_ptr<Stream> CreateNewOutgoingStream (std::shared_ptr<const dotnet::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<dotnet::client::ClientDestination> GetOwner () const { return m_Owner; };
void SetOwner (std::shared_ptr<dotnet::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<DNNPMessage> CreateDataMessage (const uint8_t * payload, size_t len, uint16_t toPort);
Packet * NewPacket () { return m_PacketsPool.Acquire(); }
void DeletePacket (Packet * p) { return m_PacketsPool.Release(p); }
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);
private:
std::shared_ptr<dotnet::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
dotnet::util::MemoryPool<Packet> m_PacketsPool;
public:
dotnet::data::GzipInflator m_Inflator;
dotnet::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 ([s, buffer, handler, timeout](void)
{
if (!s->m_ReceiveQueue.empty () || s->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));
int left = timeout - t;
auto self = s->shared_from_this();
self->m_ReceiveTimer.async_wait (
[self, buffer, handler, left](const boost::system::error_code & ec)
{
self->HandleReceiveTimer(ec, buffer, handler, left);
});
}
});
}
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

96
libdotnet/Tag.h Normal file
View file

@ -0,0 +1,96 @@
#ifndef TAG_H__
#define TAG_H__
/*
* Copyright (c) 2013-2017, The PurpleI2P Project
*
* This file is part of Purple dotnet 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 dotnet {
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 !(*this == other); }
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 = dotnet::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 = dotnet::data::ByteStreamToBase32 (m_Buf, sz, str, sz*2);
return std::string (str, str + l);
}
void FromBase32 (const std::string& s)
{
dotnet::data::Base32ToByteStream (s.c_str (), s.length (), m_Buf, sz);
}
void FromBase64 (const std::string& s)
{
dotnet::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
} // dotnet
#endif /* TAG_H__ */

203
libdotnet/Timestamp.cpp Normal file
View file

@ -0,0 +1,203 @@
#include <time.h>
#include <stdio.h>
#include <inttypes.h>
#include <string.h>
#include <chrono>
#include <future>
#include <boost/asio.hpp>
#include <boost/algorithm/string.hpp>
#include "Config.h"
#include "Log.h"
#include "DotNetEndian.h"
#include "Timestamp.h"
#ifdef WIN32
#ifndef _WIN64
#define _USE_32BIT_TIME_T
#endif
#endif
namespace dotnet
{
namespace util
{
static uint64_t GetLocalMillisecondsSinceEpoch ()
{
return std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch()).count ();
}
static uint32_t GetLocalHoursSinceEpoch ()
{
return std::chrono::duration_cast<std::chrono::hours>(
std::chrono::system_clock::now().time_since_epoch()).count ();
}
static uint64_t GetLocalSecondsSinceEpoch ()
{
return std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch()).count ();
}
static int64_t g_TimeOffset = 0; // in seconds
static void SyncTimeWithNTP (const std::string& address)
{
LogPrint (eLogInfo, "Timestamp: NTP request to ", 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, "Timestamp: NTP error: ", e.what ());
}
if (len >= 8)
{
auto ourTs = GetLocalSecondsSinceEpoch ();
uint32_t ts = bufbe32toh (buf + 32);
if (ts > 2208988800U) ts -= 2208988800U; // 1/1/1970 from 1/1/1900
g_TimeOffset = ts - ourTs;
LogPrint (eLogInfo, "Timestamp: ", address, " time offset from system time is ", g_TimeOffset, " seconds");
}
}
else
LogPrint (eLogError, "Timestamp: Couldn't open UDP socket");
}
else
LogPrint (eLogError, "Timestamp: Couldn't resove address ", address);
}
NTPTimeSync::NTPTimeSync (): m_IsRunning (false), m_Timer (m_Service)
{
dotnet::config::GetOption("nettime.ntpsyncinterval", m_SyncInterval);
std::string ntpservers; dotnet::config::GetOption("nettime.ntpservers", ntpservers);
boost::split (m_NTPServersList, ntpservers, boost::is_any_of(","), boost::token_compress_on);
}
NTPTimeSync::~NTPTimeSync ()
{
Stop ();
}
void NTPTimeSync::Start()
{
if (m_NTPServersList.size () > 0)
{
m_IsRunning = true;
LogPrint(eLogInfo, "Timestamp: NTP time sync starting");
m_Service.post (std::bind (&NTPTimeSync::Sync, this));
m_Thread.reset (new std::thread (std::bind (&NTPTimeSync::Run, this)));
}
else
LogPrint (eLogWarning, "Timestamp: No NTP server found");
}
void NTPTimeSync::Stop ()
{
if (m_IsRunning)
{
LogPrint(eLogInfo, "Timestamp: NTP time sync stopping");
m_IsRunning = false;
m_Timer.cancel ();
m_Service.stop ();
if (m_Thread)
{
m_Thread->join ();
m_Thread.reset (nullptr);
}
}
}
void NTPTimeSync::Run ()
{
while (m_IsRunning)
{
try
{
m_Service.run ();
}
catch (std::exception& ex)
{
LogPrint (eLogError, "Timestamp: NTP time sync exception: ", ex.what ());
}
}
}
void NTPTimeSync::Sync ()
{
if (m_NTPServersList.size () > 0)
SyncTimeWithNTP (m_NTPServersList[rand () % m_NTPServersList.size ()]);
else
m_IsRunning = false;
if (m_IsRunning)
{
m_Timer.expires_from_now (boost::posix_time::hours (m_SyncInterval));
m_Timer.async_wait ([this](const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
Sync ();
});
}
}
uint64_t GetMillisecondsSinceEpoch ()
{
return GetLocalMillisecondsSinceEpoch () + g_TimeOffset*1000;
}
uint32_t GetHoursSinceEpoch ()
{
return GetLocalHoursSinceEpoch () + g_TimeOffset/3600;
}
uint64_t GetSecondsSinceEpoch ()
{
return GetLocalSecondsSinceEpoch () + g_TimeOffset;
}
void GetCurrentDate (char * date)
{
GetDateString (GetSecondsSinceEpoch (), date);
}
void GetDateString (uint64_t timestamp, char * date)
{
using clock = std::chrono::system_clock;
auto t = clock::to_time_t (clock::time_point (std::chrono::seconds(timestamp)));
struct tm tm;
#ifdef _WIN32
gmtime_s(&tm, &t);
sprintf_s(date, 9, "%04i%02i%02i", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
#else
gmtime_r(&t, &tm);
sprintf(date, "%04i%02i%02i", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
#endif
}
}
}

49
libdotnet/Timestamp.h Normal file
View file

@ -0,0 +1,49 @@
#ifndef TIMESTAMP_H__
#define TIMESTAMP_H__
#include <inttypes.h>
#include <thread>
#include <vector>
#include <string>
#include <boost/asio.hpp>
namespace dotnet
{
namespace util
{
uint64_t GetMillisecondsSinceEpoch ();
uint32_t GetHoursSinceEpoch ();
uint64_t GetSecondsSinceEpoch ();
void GetCurrentDate (char * date); // returns date as YYYYMMDD string, 9 bytes
void GetDateString (uint64_t timestamp, char * date); // timestap is seconds since epoch, returns date as YYYYMMDD string, 9 bytes
class NTPTimeSync
{
public:
NTPTimeSync ();
~NTPTimeSync ();
void Start ();
void Stop ();
private:
void Run ();
void Sync ();
private:
bool m_IsRunning;
std::unique_ptr<std::thread> m_Thread;
boost::asio::io_service m_Service;
boost::asio::deadline_timer m_Timer;
int m_SyncInterval;
std::vector<std::string> m_NTPServersList;
};
}
}
#endif

111
libdotnet/TransitTunnel.cpp Normal file
View file

@ -0,0 +1,111 @@
#include <string.h>
#include "DotNetEndian.h"
#include "Log.h"
#include "RouterContext.h"
#include "DNNPProtocol.h"
#include "Tunnel.h"
#include "Transports.h"
#include "TransitTunnel.h"
namespace dotnet
{
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 DNNPMessage> in, std::shared_ptr<DNNPMessage> out)
{
m_Encryption.Encrypt (in->GetPayload () + 4, out->GetPayload () + 4);
dotnet::transport::transports.UpdateTotalTransitTransmittedBytes (TUNNEL_DATA_MSG_SIZE);
}
TransitTunnelParticipant::~TransitTunnelParticipant ()
{
}
void TransitTunnelParticipant::HandleTunnelDataMsg (std::shared_ptr<const dotnet::DNNPMessage> tunnelMsg)
{
auto newMsg = CreateEmptyTunnelDataMsg ();
EncryptTunnelMsg (tunnelMsg, newMsg);
m_NumTransmittedBytes += tunnelMsg->GetLength ();
htobe32buf (newMsg->GetPayload (), GetNextTunnelID ());
newMsg->FillDNNPMessageHeader (eDNNPTunnelData);
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);
dotnet::transport::transports.SendMessages (GetNextIdentHash (), m_TunnelDataMsgs);
m_TunnelDataMsgs.clear ();
}
}
void TransitTunnel::SendTunnelDataMsg (std::shared_ptr<dotnet::DNNPMessage> msg)
{
LogPrint (eLogError, "TransitTunnel: We are not a gateway for ", GetTunnelID ());
}
void TransitTunnel::HandleTunnelDataMsg (std::shared_ptr<const dotnet::DNNPMessage> tunnelMsg)
{
LogPrint (eLogError, "TransitTunnel: Incoming tunnel message is not supported ", GetTunnelID ());
}
void TransitTunnelGateway::SendTunnelDataMsg (std::shared_ptr<dotnet::DNNPMessage> 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 dotnet::DNNPMessage> 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
libdotnet/TransitTunnel.h Normal file
View 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 "DNNPProtocol.h"
#include "TunnelEndpoint.h"
#include "TunnelGateway.h"
#include "TunnelBase.h"
namespace dotnet
{
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<dotnet::DNNPMessage> msg);
void HandleTunnelDataMsg (std::shared_ptr<const dotnet::DNNPMessage> tunnelMsg);
void EncryptTunnelMsg (std::shared_ptr<const DNNPMessage> in, std::shared_ptr<DNNPMessage> out);
private:
dotnet::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 dotnet::DNNPMessage> tunnelMsg);
void FlushTunnelDataMsgs ();
private:
size_t m_NumTransmittedBytes;
std::vector<std::shared_ptr<dotnet::DNNPMessage> > 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<dotnet::DNNPMessage> 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 dotnet::DNNPMessage> 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

View file

@ -0,0 +1,97 @@
#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 "DNNPProtocol.h"
#include "Timestamp.h"
namespace dotnet
{
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 dotnet::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 dotnet::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 dotnet::data::RouterInfo> router, int terminationTimeout):
m_DHKeysPair (nullptr), m_NumSentBytes (0), m_NumReceivedBytes (0), m_IsOutgoing (router), m_TerminationTimeout (terminationTimeout),
m_LastActivityTimestamp (dotnet::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 dotnet::data::IdentityEx> GetRemoteIdentity () { return m_RemoteIdentity; };
void SetRemoteIdentity (std::shared_ptr<const dotnet::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 SendLocalRouterInfo () { SendDNNPMessages ({ CreateDatabaseStoreMsg () }); };
virtual void SendDNNPMessages (const std::vector<std::shared_ptr<DNNPMessage> >& msgs) = 0;
protected:
std::shared_ptr<const dotnet::data::IdentityEx> m_RemoteIdentity;
std::shared_ptr<dotnet::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

798
libdotnet/Transports.cpp Normal file
View file

@ -0,0 +1,798 @@
#include "Log.h"
#include "Crypto.h"
#include "RouterContext.h"
#include "DNNPProtocol.h"
#include "NetDb.hpp"
#include "Transports.h"
#include "Config.h"
#include "HTTP.h"
#ifdef WITH_EVENTS
#include "Event.h"
#include "util.h"
#endif
using namespace dotnet::data;
namespace dotnet
{
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 ()
{
{
std::unique_lock<std::mutex> l(m_AcquiredMutex);
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 < 10)
{
CreateDHKeysPairs (num);
total += num;
}
if (total >= 10)
{
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);
if (!m_IsRunning) break;
m_Acquired.wait (l); // wait for element gets acquired
}
}
}
void DHKeysPairSupplier::CreateDHKeysPairs (int num)
{
if (num > 0)
{
for (int i = 0; i < num; i++)
{
auto pair = std::make_shared<dotnet::crypto::DHKeys> ();
pair->GenerateKeys ();
std::unique_lock<std::mutex> l(m_AcquiredMutex);
m_Queue.push (pair);
}
}
}
std::shared_ptr<dotnet::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<dotnet::crypto::DHKeys> ();
pair->GenerateKeys ();
return pair;
}
void DHKeysPairSupplier::Return (std::shared_ptr<dotnet::crypto::DHKeys> pair)
{
if (pair)
{
std::unique_lock<std::mutex>l(m_AcquiredMutex);
if ((int)m_Queue.size () < 2*m_QueueSize)
m_Queue.push (pair);
}
else
LogPrint(eLogError, "Transports: return null DHKeys");
}
Transports transports;
Transports::Transports ():
m_IsOnline (true), m_IsRunning (false), m_IsNAT (true), m_Thread (nullptr), m_Service (nullptr),
m_Work (nullptr), m_PeerCleanupTimer (nullptr), m_PeerTestTimer (nullptr),
m_NTCPServer (nullptr), m_SSUServer (nullptr), m_NTCP2Server (nullptr),
m_DHKeysPairSupplier (5), // 5 pre-generated keys
m_TotalSentBytes(0), m_TotalReceivedBytes(0), m_TotalTransitTransmittedBytes (0),
m_InBandwidth (0), m_OutBandwidth (0), m_TransitBandwidth(0),
m_LastInBandwidthUpdateBytes (0), m_LastOutBandwidthUpdateBytes (0),
m_LastTransitBandwidthUpdateBytes (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);
}
dotnet::config::GetOption("nat", m_IsNAT);
m_DHKeysPairSupplier.Start ();
m_IsRunning = true;
m_Thread = new std::thread (std::bind (&Transports::Run, this));
std::string ntcpproxy; dotnet::config::GetOption("ntcpproxy", ntcpproxy);
dotnet::http::URL proxyurl;
uint16_t softLimit, hardLimit, threads;
dotnet::config::GetOption("limits.ntcpsoft", softLimit);
dotnet::config::GetOption("limits.ntcphard", hardLimit);
dotnet::config::GetOption("limits.ntcpthreads", threads);
if(softLimit > 0 && hardLimit > 0 && softLimit >= hardLimit)
{
LogPrint(eLogError, "ntcp soft limit must be less than ntcp hard limit");
return;
}
if(ntcpproxy.size() && enableNTCP)
{
if(proxyurl.parse(ntcpproxy))
{
if(proxyurl.schema == "socks" || proxyurl.schema == "http")
{
m_NTCPServer = new NTCPServer(threads);
m_NTCPServer->SetSessionLimits(softLimit, hardLimit);
NTCPServer::ProxyType proxytype = NTCPServer::eSocksProxy;
if (proxyurl.schema == "http")
proxytype = NTCPServer::eHTTPProxy;
m_NTCPServer->UseProxy(proxytype, proxyurl.host, proxyurl.port) ;
m_NTCPServer->Start();
if(!m_NTCPServer->NetworkIsReady())
{
LogPrint(eLogError, "Transports: NTCP failed to start with proxy");
m_NTCPServer->Stop();
delete m_NTCPServer;
m_NTCPServer = nullptr;
}
}
else
LogPrint(eLogError, "Transports: unsupported NTCP proxy URL ", ntcpproxy);
}
else
LogPrint(eLogError, "Transports: invalid NTCP proxy url ", ntcpproxy);
return;
}
// create NTCP2. TODO: move to acceptor
bool ntcp2; dotnet::config::GetOption("ntcp2.enabled", ntcp2);
if (ntcp2)
{
m_NTCP2Server = new NTCP2Server ();
m_NTCP2Server->Start ();
}
// create acceptors
auto& addresses = context.GetRouterInfo ().GetAddresses ();
for (const auto& address : addresses)
{
if (!address) continue;
if (m_NTCPServer == nullptr && enableNTCP)
{
m_NTCPServer = new NTCPServer (threads);
m_NTCPServer->SetSessionLimits(softLimit, hardLimit);
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));
if (m_IsNAT)
{
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;
}
if (m_NTCP2Server)
{
m_NTCP2Server->Stop ();
delete m_NTCP2Server;
m_NTCP2Server = 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 = dotnet::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_TransitBandwidth = (m_TotalTransitTransmittedBytes - m_LastTransitBandwidthUpdateBytes)*1000/delta;
}
}
m_LastBandwidthUpdateTime = ts;
m_LastInBandwidthUpdateBytes = m_TotalReceivedBytes;
m_LastOutBandwidthUpdateBytes = m_TotalSentBytes;
m_LastTransitBandwidthUpdateBytes = m_TotalTransitTransmittedBytes;
}
bool Transports::IsBandwidthExceeded () const
{
auto limit = dotnet::context.GetBandwidthLimit() * 1024; // convert to bytes
auto bw = std::max (m_InBandwidth, m_OutBandwidth);
return bw > limit;
}
bool Transports::IsTransitBandwidthExceeded () const
{
auto limit = dotnet::context.GetTransitBandwidthLimit() * 1024; // convert to bytes
return m_TransitBandwidth > limit;
}
void Transports::SendMessage (const dotnet::data::IdentHash& ident, std::shared_ptr<dotnet::DNNPMessage> msg)
{
SendMessages (ident, std::vector<std::shared_ptr<dotnet::DNNPMessage> > {msg });
}
void Transports::SendMessages (const dotnet::data::IdentHash& ident, const std::vector<std::shared_ptr<dotnet::DNNPMessage> >& 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 (dotnet::data::IdentHash ident, std::vector<std::shared_ptr<dotnet::DNNPMessage> > msgs)
{
if (ident == dotnet::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<dotnet::data::IdentHash, Peer>(ident, { 0, r, {},
dotnet::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 ()->SendDNNPMessages (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 dotnet::data::IdentHash& ident, Peer& peer)
{
if (peer.router) // we have RI already
{
if (!peer.numAttempts) // NTCP2
{
peer.numAttempts++;
if (m_NTCP2Server) // we support NTCP2
{
// NTCP2 have priority over NTCP
auto address = peer.router->GetNTCP2Address (true, !context.SupportsV6 ()); // published only
if (address)
{
auto s = std::make_shared<NTCP2Session> (*m_NTCP2Server, peer.router);
m_NTCP2Server->Connect (address->host, address->port, s);
return true;
}
}
}
if (peer.numAttempts == 1) // NTCP1
{
peer.numAttempts++;
auto address = peer.router->GetNTCPAddress (!context.SupportsV6 ());
if (address && m_NTCPServer)
{
if (!peer.router->UsesIntroducer () && !peer.router->IsUnreachable ())
{
if(!m_NTCPServer->ShouldLimit())
{
auto s = std::make_shared<NTCPSession> (*m_NTCPServer, peer.router);
if(m_NTCPServer->UsingProxy())
{
NTCPServer::RemoteAddressType remote = NTCPServer::eIP4Address;
std::string addr = address->host.to_string();
if(address->host.is_v6())
remote = NTCPServer::eIP6Address;
m_NTCPServer->ConnectWithProxy(addr, address->port, remote, s);
}
else
m_NTCPServer->Connect (address->host, address->port, s);
return true;
}
else
{
LogPrint(eLogWarning, "Transports: NTCP Limit hit falling back to SSU");
}
}
}
else
LogPrint (eLogDebug, "Transports: NTCP address is not present for ", dotnet::data::GetIdentHashAbbreviation (ident), ", trying SSU");
}
if (peer.numAttempts == 2)// SSU
{
peer.numAttempts++;
if (m_SSUServer && peer.router->IsSSU (!context.SupportsV6 ()))
{
auto address = peer.router->GetSSUAddress (!context.SupportsV6 ());
m_SSUServer->CreateSession (peer.router, address->host, address->port);
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");
dotnet::data::netdb.RequestDestination (ident, std::bind (
&Transports::RequestComplete, this, std::placeholders::_1, ident));
}
return true;
}
void Transports::RequestComplete (std::shared_ptr<const dotnet::data::RouterInfo> r, const dotnet::data::IdentHash& ident)
{
m_Service->post (std::bind (&Transports::HandleRequestComplete, this, r, ident));
}
void Transports::HandleRequestComplete (std::shared_ptr<const dotnet::data::RouterInfo> r, dotnet::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::CloseSession (std::shared_ptr<const dotnet::data::RouterInfo> router)
{
if (!router) return;
m_Service->post (std::bind (&Transports::PostCloseSession, this, router));
}
void Transports::PostCloseSession (std::shared_ptr<const dotnet::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");
dotnet::context.SetStatus (eRouterStatusOK);
return;
}
if (m_SSUServer)
{
bool isv4 = dotnet::context.SupportsV4 ();
if (m_IsNAT && isv4)
dotnet::context.SetStatus (eRouterStatusTesting);
for (int i = 0; i < 5; i++)
{
auto router = dotnet::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 = dotnet::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() || !dotnet::context.SupportsV4 ()) return;
if (m_SSUServer)
{
bool statusChanged = false;
for (int i = 0; i < 5; i++)
{
auto router = dotnet::data::netdb.GetRandomPeerTestRouter (true); // v4 only
if (router)
{
if (!statusChanged)
{
statusChanged = true;
dotnet::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<dotnet::crypto::DHKeys> Transports::GetNextDHKeysPair ()
{
return m_DHKeysPairSupplier.Acquire ();
}
void Transports::ReuseDHKeysPair (std::shared_ptr<dotnet::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 () == eDNNPDatabaseStore &&
dotnet::data::IdentHash(firstMsg->GetPayload () + DATABASE_STORE_KEY_OFFSET) == dotnet::context.GetIdentHash ())
sendDatabaseStore = false; // we have it in the list already
}
if (sendDatabaseStore)
session->SendLocalRouterInfo ();
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->SendDNNPMessages (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->SendDNNPMessages ({ CreateDatabaseStoreMsg () }); // send DatabaseStore
std::unique_lock<std::mutex> l(m_PeersMutex);
m_Peers.insert (std::make_pair (ident, Peer{ 0, nullptr, { session }, dotnet::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 dotnet::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 = dotnet::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 = dotnet::data::GetRouterProfile(it->first);
if (profile)
{
profile->TunnelNonReplied();
}
std::unique_lock<std::mutex> l(m_PeersMutex);
it = m_Peers.erase (it);
}
else
++it;
}
UpdateBandwidth (); // TODO: use separate timer(s) for it
if (dotnet::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 dotnet::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<dotnet::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 dotnet::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 dotnet::data::netdb.GetRandomRouterInFamily(fam);
}
{
std::unique_lock<std::mutex> l(m_TrustedRoutersMutex);
auto sz = m_TrustedRouters.size();
if (sz)
{
if(sz == 1)
return dotnet::data::netdb.FindRouter(m_TrustedRouters[0]);
auto it = m_TrustedRouters.begin();
std::advance(it, rand() % sz);
return dotnet::data::netdb.FindRouter(*it);
}
}
return nullptr;
}
bool Transports::IsRestrictedPeer(const dotnet::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 = dotnet::data::netdb.FindRouter(ih);
for (const auto & fam : m_TrustedFamilies)
if(ri->IsFamily(fam)) return true;
}
return false;
}
}
}

186
libdotnet/Transports.h Normal file
View file

@ -0,0 +1,186 @@
#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 "NTCP2.h"
#include "RouterInfo.h"
#include "DNNPProtocol.h"
#include "Identity.h"
namespace dotnet
{
namespace transport
{
class DHKeysPairSupplier
{
public:
DHKeysPairSupplier (int size);
~DHKeysPairSupplier ();
void Start ();
void Stop ();
std::shared_ptr<dotnet::crypto::DHKeys> Acquire ();
void Return (std::shared_ptr<dotnet::crypto::DHKeys> pair);
private:
void Run ();
void CreateDHKeysPairs (int num);
private:
const int m_QueueSize;
std::queue<std::shared_ptr<dotnet::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 dotnet::data::RouterInfo> router;
std::list<std::shared_ptr<TransportSession> > sessions;
uint64_t creationTime;
std::vector<std::shared_ptr<dotnet::DNNPMessage> > 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 IsBoundNTCP2() const { return m_NTCP2Server != 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<dotnet::crypto::DHKeys> GetNextDHKeysPair ();
void ReuseDHKeysPair (std::shared_ptr<dotnet::crypto::DHKeys> pair);
void SendMessage (const dotnet::data::IdentHash& ident, std::shared_ptr<dotnet::DNNPMessage> msg);
void SendMessages (const dotnet::data::IdentHash& ident, const std::vector<std::shared_ptr<dotnet::DNNPMessage> >& msgs);
void CloseSession (std::shared_ptr<const dotnet::data::RouterInfo> router);
void PeerConnected (std::shared_ptr<TransportSession> session);
void PeerDisconnected (std::shared_ptr<TransportSession> session);
bool IsConnected (const dotnet::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; };
uint64_t GetTotalTransitTransmittedBytes () const { return m_TotalTransitTransmittedBytes; }
void UpdateTotalTransitTransmittedBytes (uint32_t add) { m_TotalTransitTransmittedBytes += add; };
uint32_t GetInBandwidth () const { return m_InBandwidth; };
uint32_t GetOutBandwidth () const { return m_OutBandwidth; };
uint32_t GetTransitBandwidth () const { return m_TransitBandwidth; };
bool IsBandwidthExceeded () const;
bool IsTransitBandwidthExceeded () const;
size_t GetNumPeers () const { return m_Peers.size (); };
std::shared_ptr<const dotnet::data::RouterInfo> GetRandomPeer () const;
/** get a trusted first hop for restricted routes */
std::shared_ptr<const dotnet::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<dotnet::data::IdentHash> routers);
bool IsRestrictedPeer(const dotnet::data::IdentHash & ident) const;
void PeerTest ();
private:
void Run ();
void RequestComplete (std::shared_ptr<const dotnet::data::RouterInfo> r, const dotnet::data::IdentHash& ident);
void HandleRequestComplete (std::shared_ptr<const dotnet::data::RouterInfo> r, dotnet::data::IdentHash ident);
void PostMessages (dotnet::data::IdentHash ident, std::vector<std::shared_ptr<dotnet::DNNPMessage> > msgs);
void PostCloseSession (std::shared_ptr<const dotnet::data::RouterInfo> router);
bool ConnectToPeer (const dotnet::data::IdentHash& ident, Peer& peer);
void HandlePeerCleanupTimer (const boost::system::error_code& ecode);
void HandlePeerTestTimer (const boost::system::error_code& ecode);
void UpdateBandwidth ();
void DetectExternalIP ();
private:
bool m_IsOnline, m_IsRunning, m_IsNAT;
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;
NTCP2Server * m_NTCP2Server;
mutable std::mutex m_PeersMutex;
std::map<dotnet::data::IdentHash, Peer> m_Peers;
DHKeysPairSupplier m_DHKeysPairSupplier;
std::atomic<uint64_t> m_TotalSentBytes, m_TotalReceivedBytes, m_TotalTransitTransmittedBytes;
uint32_t m_InBandwidth, m_OutBandwidth, m_TransitBandwidth; // bytes per second
uint64_t m_LastInBandwidthUpdateBytes, m_LastOutBandwidthUpdateBytes, m_LastTransitBandwidthUpdateBytes;
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<dotnet::data::IdentHash> m_TrustedRouters;
mutable std::mutex m_TrustedRoutersMutex;
dotnet::DNNPMessagesHandler m_LoopbackHandler;
public:
// for HTTP only
const NTCPServer * GetNTCPServer () const { return m_NTCPServer; };
const SSUServer * GetSSUServer () const { return m_SSUServer; };
const NTCP2Server * GetNTCP2Server () const { return m_NTCP2Server; };
const decltype(m_Peers)& GetPeers () const { return m_Peers; };
};
extern Transports transports;
}
}
#endif

946
libdotnet/Tunnel.cpp Normal file
View file

@ -0,0 +1,946 @@
#include <string.h>
#include "DotNetEndian.h"
#include <thread>
#include <algorithm>
#include <vector>
#include "Crypto.h"
#include "RouterContext.h"
#include "Log.h"
#include "Timestamp.h"
#include "DNNPProtocol.h"
#include "Transports.h"
#include "NetDb.hpp"
#include "Config.h"
#include "Tunnel.h"
#include "TunnelPool.h"
#ifdef WITH_EVENTS
#include "Event.h"
#endif
namespace dotnet
{
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 = dotnet::context.GetIdentity()->GetIdentHash().ToBase64();
#endif
auto numHops = m_Config->GetNumHops ();
int numRecords = numHops <= STANDARD_NUM_RECORDS ? STANDARD_NUM_RECORDS : numHops;
auto msg = NewDNNPShortMessage ();
*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
dotnet::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->FillDNNPMessageHeader (eDNNPVariableTunnelBuild);
// send message
if (outboundTunnel)
outboundTunnel->SendTunnelDataMsg (GetNextIdentHash (), 0, msg);
else
dotnet::transport::transports.SendMessage (GetNextIdentHash (), msg);
}
bool Tunnel::HandleTunnelBuildResponse (uint8_t * msg, size_t len)
{
LogPrint (eLogDebug, "Tunnel: TunnelBuildResponse ", (int)msg[0], " records.");
dotnet::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 = dotnet::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 DNNPMessage> in, std::shared_ptr<DNNPMessage> 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<dotnet::DNNPMessage> msg)
{
LogPrint (eLogWarning, "Tunnel: Can't send DNNP messages without delivery instructions");
}
std::vector<std::shared_ptr<const dotnet::data::IdentityEx> > Tunnel::GetPeers () const
{
auto peers = GetInvertedPeers ();
std::reverse (peers.begin (), peers.end ());
return peers;
}
std::vector<std::shared_ptr<const dotnet::data::IdentityEx> > Tunnel::GetInvertedPeers () const
{
// hops are in inverted order
std::vector<std::shared_ptr<const dotnet::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 << " &#8658; ";
s << dotnet::data::GetIdentHashAbbreviation ((*it)->ident->GetIdentHash ());
}
}
void InboundTunnel::HandleTunnelDataMsg (std::shared_ptr<const DNNPMessage> 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 << " &#8658; " << GetTunnelID () << ":me";
}
ZeroHopsInboundTunnel::ZeroHopsInboundTunnel ():
InboundTunnel (std::make_shared<ZeroHopsTunnelConfig> ()),
m_NumReceivedBytes (0)
{
}
void ZeroHopsInboundTunnel::SendTunnelDataMsg (std::shared_ptr<dotnet::DNNPMessage> msg)
{
if (msg)
{
m_NumReceivedBytes += msg->GetLength ();
msg->from = shared_from_this ();
HandleDNNPMessage (msg);
}
}
void ZeroHopsInboundTunnel::Print (std::stringstream& s) const
{
s << " &#8658; " << GetTunnelID () << ":me";
}
void OutboundTunnel::SendTunnelDataMsg (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr<dotnet::DNNPMessage> 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 dotnet::DNNPMessage> tunnelMsg)
{
LogPrint (eLogError, "Tunnel: incoming message for outbound tunnel ", GetTunnelID ());
}
void OutboundTunnel::Print (std::stringstream& s) const
{
s << GetTunnelID () << ":me";
PrintHops (s);
s << " &#8658; ";
}
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:
dotnet::HandleDNNPMessage (msg.data);
break;
case eDeliveryTypeTunnel:
dotnet::transport::transports.SendMessage (msg.hash, dotnet::CreateTunnelGatewayMsg (msg.tunnelID, msg.data));
break;
case eDeliveryTypeRouter:
dotnet::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 &#8658; ";
}
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 eDNNPTunnelData:
case eDNNPTunnelGateway:
{
tunnelID = bufbe32toh (msg->GetPayload ());
if (tunnelID == prevTunnelID)
tunnel = prevTunnel;
else if (prevTunnel)
prevTunnel->FlushTunnelDataMsgs ();
if (!tunnel)
tunnel = GetTunnel (tunnelID);
if (tunnel)
{
if (typeID == eDNNPTunnelData)
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 eDNNPVariableTunnelBuild:
case eDNNPVariableTunnelBuildReply:
case eDNNPTunnelBuild:
case eDNNPTunnelBuildReply:
HandleDNNPMessage (msg->GetBuffer (), msg->GetLength ());
break;
default:
LogPrint (eLogWarning, "Tunnel: unexpected message type ", (int) typeID);
}
msg = m_Queue.Get ();
if (msg)
{
prevTunnelID = tunnelID;
prevTunnel = tunnel;
}
else if (tunnel)
tunnel->FlushTunnelDataMsgs ();
}
while (msg);
}
uint64_t ts = dotnet::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<DNNPMessage> 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 DNNP message to send
msg->offset += DNNP_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 == eDNNPDatabaseSearchReply)
// transit DatabaseStore my contain new/updated RI
// or DatabaseSearchReply with new routers
dotnet::data::netdb.PostDNNPMsg (CopyDNNPMessage (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 = dotnet::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 = dotnet::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 = dotnet::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)
{
auto pool = tunnel->GetTunnelPool ();
// let it die if the tunnel pool has been reconfigured and this is old
if (pool && tunnel->GetNumHops() == pool->GetNumOutboundHops())
{
tunnel->SetIsRecreated ();
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 = dotnet::transport::transports.RoutesRestricted() ?
dotnet::transport::transports.GetRestrictedPeer() :
dotnet::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 dotnet::data::IdentityEx> > { router->GetRouterIdentity () },
inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ())
);
}
}
void Tunnels::ManageInboundTunnels ()
{
uint64_t ts = dotnet::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)
{
auto pool = tunnel->GetTunnelPool ();
// let it die if the tunnel pool was reconfigured and has different number of hops
if (pool && tunnel->GetNumHops() == pool->GetNumInboundHops())
{
tunnel->SetIsRecreated ();
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; dotnet::config::GetOption("exploratory.inbound.length", ibLen);
int obLen; dotnet::config::GetOption("exploratory.outbound.length", obLen);
int ibNum; dotnet::config::GetOption("exploratory.inbound.quantity", ibNum);
int obNum; dotnet::config::GetOption("exploratory.outbound.quantity", obNum);
m_ExploratoryPool = CreateTunnelPool (ibLen, obLen, ibNum, obNum);
m_ExploratoryPool->SetLocalDestination (dotnet::context.GetSharedDestination ());
}
return;
}
if (m_OutboundTunnels.empty () || m_InboundTunnels.size () < 3)
{
// trying to create one more inbound tunnel
auto router = dotnet::transport::transports.RoutesRestricted() ?
dotnet::transport::transports.GetRestrictedPeer() :
dotnet::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 dotnet::data::IdentityEx> > { router->GetRouterIdentity () })
);
}
}
void Tunnels::ManageTransitTunnels ()
{
uint32_t ts = dotnet::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<DNNPMessage> msg)
{
if (msg) m_Queue.Put (msg);
}
void Tunnels::PostTunnelData (const std::vector<std::shared_ptr<DNNPMessage> >& 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 = dotnet::util::GetSecondsSinceEpoch ();
// TODO: possible race condition with DotNetControl
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();
}
}
}

309
libdotnet/Tunnel.h Normal file
View file

@ -0,0 +1,309 @@
#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 "DNNPProtocol.h"
#include "Event.h"
namespace dotnet
{
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 dotnet::data::IdentityEx> ident;
dotnet::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 dotnet::data::IdentityEx> > GetPeers () const;
std::vector<std::shared_ptr<const dotnet::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; };
int GetNumHops () const { return m_Hops.size (); };
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<dotnet::DNNPMessage> msg);
void EncryptTunnelMsg (std::shared_ptr<const DNNPMessage> in, std::shared_ptr<DNNPMessage> 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; }
/** @brief 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<dotnet::DNNPMessage> msg);
virtual void SendTunnelDataMsg (const std::vector<TunnelMessageBlock>& msgs); // multiple messages
const dotnet::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 dotnet::DNNPMessage> tunnelMsg);
bool IsInbound() const { return false; }
private:
std::mutex m_SendMutex;
TunnelGateway m_Gateway;
dotnet::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 DNNPMessage> 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<dotnet::DNNPMessage> 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<DNNPMessage> msg);
void PostTunnelData (const std::vector<std::shared_ptr<DNNPMessage> >& 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<DNNPMessage> 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;
dotnet::util::Queue<std::shared_ptr<DNNPMessage> > 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

74
libdotnet/TunnelBase.h Normal file
View file

@ -0,0 +1,74 @@
#ifndef TUNNEL_BASE_H__
#define TUNNEL_BASE_H__
#include <inttypes.h>
#include <memory>
#include "Timestamp.h"
#include "DNNPProtocol.h"
#include "Identity.h"
namespace dotnet
{
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;
dotnet::data::IdentHash hash;
uint32_t tunnelID;
std::shared_ptr<DNNPMessage> data;
};
class TunnelBase
{
public:
TunnelBase (uint32_t tunnelID, uint32_t nextTunnelID, dotnet::data::IdentHash nextIdent):
m_TunnelID (tunnelID), m_NextTunnelID (nextTunnelID), m_NextIdent (nextIdent),
m_CreationTime (dotnet::util::GetSecondsSinceEpoch ()) {};
virtual ~TunnelBase () {};
virtual void Cleanup () {};
virtual void HandleTunnelDataMsg (std::shared_ptr<const dotnet::DNNPMessage> tunnelMsg) = 0;
virtual void SendTunnelDataMsg (std::shared_ptr<dotnet::DNNPMessage> msg) = 0;
virtual void FlushTunnelDataMsgs () {};
virtual void EncryptTunnelMsg (std::shared_ptr<const DNNPMessage> in, std::shared_ptr<DNNPMessage> out) = 0;
uint32_t GetNextTunnelID () const { return m_NextTunnelID; };
const dotnet::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;
dotnet::data::IdentHash m_NextIdent;
uint32_t m_CreationTime; // seconds since epoch
};
struct TunnelCreationTimeCmp
{
template<typename T>
bool operator() (const std::shared_ptr<T> & t1, const std::shared_ptr<T> & t2) const
{
if (t1->GetCreationTime () != t2->GetCreationTime ())
return t1->GetCreationTime () > t2->GetCreationTime ();
else
return t1 < t2;
}
};
}
}
#endif

255
libdotnet/TunnelConfig.h Normal file
View file

@ -0,0 +1,255 @@
#ifndef TUNNEL_CONFIG_H__
#define TUNNEL_CONFIG_H__
#include <inttypes.h>
#include <sstream>
#include <vector>
#include <memory>
#include "Identity.h"
#include "RouterContext.h"
#include "Timestamp.h"
namespace dotnet
{
namespace tunnel
{
struct TunnelHopConfig
{
std::shared_ptr<const dotnet::data::IdentityEx> ident;
dotnet::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 dotnet::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);
if (!tunnelID) tunnelID = 1; // tunnelID can't be zero
isGateway = true;
isEndpoint = true;
ident = r;
//nextRouter = nullptr;
nextTunnelID = 0;
next = nullptr;
prev = nullptr;
}
void SetNextIdent (const dotnet::data::IdentHash& ident)
{
nextIdent = ident;
isEndpoint = false;
RAND_bytes ((uint8_t *)&nextTunnelID, 4);
if (!nextTunnelID) nextTunnelID = 1; // tunnelID can't be zero
}
void SetReplyHop (uint32_t replyTunnelID, const dotnet::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, dotnet::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);
auto encryptor = ident->CreateEncryptor (nullptr);
if (encryptor)
encryptor->Encrypt (clearText, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, ctx, false);
memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16);
}
};
class TunnelConfig
{
public:
TunnelConfig (std::vector<std::shared_ptr<const dotnet::data::IdentityEx> > peers) // inbound
{
CreatePeers (peers);
m_LastHop->SetNextIdent (dotnet::context.GetIdentHash ());
}
TunnelConfig (std::vector<std::shared_ptr<const dotnet::data::IdentityEx> > peers,
uint32_t replyTunnelID, const dotnet::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 dotnet::data::IdentHash& GetNextIdentHash () const
{
return m_FirstHop->ident->GetIdentHash ();
}
virtual const dotnet::data::IdentHash& GetLastIdentHash () const
{
return m_LastHop->ident->GetIdentHash ();
}
std::vector<std::shared_ptr<const dotnet::data::IdentityEx> > GetPeers () const
{
std::vector<std::shared_ptr<const dotnet::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 dotnet::data::IdentHash& GetNextIdentHash () const { return dotnet::context.GetIdentHash (); };
const dotnet::data::IdentHash& GetLastIdentHash () const { return dotnet::context.GetIdentHash (); };
private:
uint32_t m_TunnelID;
};
}
}
#endif

View file

@ -0,0 +1,298 @@
#include "DotNetEndian.h"
#include <string.h>
#include "Crypto.h"
#include "Log.h"
#include "NetDb.hpp"
#include "DNNPProtocol.h"
#include "Transports.h"
#include "RouterContext.h"
#include "Timestamp.h"
#include "TunnelEndpoint.h"
namespace dotnet
{
namespace tunnel
{
TunnelEndpoint::~TunnelEndpoint ()
{
}
void TunnelEndpoint::HandleDecryptedTunnelDataMsg (std::shared_ptr<DNNPMessage> 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 = dotnet::data::IdentHash (fragment);
fragment += 32; // hash
break;
case eDeliveryTypeRouter: // 2
m.hash = dotnet::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 = NewDNNPTunnelMessage ();
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 = dotnet::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 < DNNP_MAX_MESSAGE_SIZE) // check if message is not too long
{
if (msg.data->len + size > msg.data->maxLen)
{
// LogPrint (eLogWarning, "TunnelMessage: DNNP message size ", msg.data->maxLen, " is not enough");
auto newMsg = NewDNNPMessage ();
*newMsg = *(msg.data);
msg.data = newMsg;
}
if (msg.data->Concat (fragment, size) < size) // concatenate fragment
LogPrint (eLogError, "TunnelMessage: DNNP 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 DNNP 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<DNNPMessage> data)
{
if (!m_OutOfSequenceFragments.insert ({{msgID, fragmentNum}, {isLastFragment, data, dotnet::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 DNNP message size ", msg.data->maxLen, " is not enough");
auto newMsg = NewDNNPMessage ();
*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 DNNP 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 == eDNNPDatabaseSearchReply) &&
!m_IsInbound && msg.deliveryType != eDeliveryTypeLocal)
dotnet::data::netdb.PostDNNPMsg (CopyDNNPMessage (msg.data));
switch (msg.deliveryType)
{
case eDeliveryTypeLocal:
dotnet::HandleDNNPMessage (msg.data);
break;
case eDeliveryTypeTunnel:
if (!m_IsInbound) // outbound transit tunnel
dotnet::transport::transports.SendMessage (msg.hash, dotnet::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
dotnet::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 = dotnet::util::GetMillisecondsSinceEpoch ();
// out-of-sequence fragments
for (auto it = m_OutOfSequenceFragments.begin (); it != m_OutOfSequenceFragments.end ();)
{
if (ts > it->second.receiveTime + dotnet::DNNP_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 + dotnet::DNNP_MESSAGE_EXPIRATION_TIMEOUT)
it = m_IncompleteMessages.erase (it);
else
++it;
}
}
}
}

View file

@ -0,0 +1,57 @@
#ifndef TUNNEL_ENDPOINT_H__
#define TUNNEL_ENDPOINT_H__
#include <inttypes.h>
#include <map>
#include <string>
#include "DNNPProtocol.h"
#include "TunnelBase.h"
namespace dotnet
{
namespace tunnel
{
class TunnelEndpoint
{
struct TunnelMessageBlockEx: public TunnelMessageBlock
{
uint64_t receiveTime; // milliseconds since epoch
uint8_t nextFragmentNum;
};
struct Fragment
{
bool isLastFragment;
std::shared_ptr<DNNPMessage> 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<DNNPMessage> 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<DNNPMessage> 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

222
libdotnet/TunnelGateway.cpp Normal file
View file

@ -0,0 +1,222 @@
#include <string.h>
#include "Crypto.h"
#include "DotNetEndian.h"
#include "Log.h"
#include "RouterContext.h"
#include "Transports.h"
#include "TunnelGateway.h"
namespace dotnet
{
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::PutDNNPMsg (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<DNNPMessage> & msg = block.data;
size_t fullMsgLen = diLen + msg->GetLength () + 2; // delivery instructions + payload + 2 bytes length
if (!messageCreated && fullMsgLen > m_RemainingSize) // check if we should complete previous message
{
size_t numFollowOnFragments = fullMsgLen / TUNNEL_DATA_MAX_PAYLOAD_SIZE;
// length of bytes doesn'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 (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 (diLen + 6 <= m_RemainingSize)
{
// delivery instructions fit
uint32_t msgID;
memcpy (&msgID, msg->GetHeader () + DNNP_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 ();
PutDNNPMsg (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 = NewDNNPShortMessage ();
m_CurrentTunnelDataMsg->Align (12);
// we reserve space for padding
m_CurrentTunnelDataMsg->offset += TUNNEL_DATA_MSG_SIZE + DNNP_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 - DNNP_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.PutDNNPMsg (block);
}
void TunnelGateway::SendBuffer ()
{
m_Buffer.CompleteCurrentTunnelDataMessage ();
std::vector<std::shared_ptr<DNNPMessage> > 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->FillDNNPMessageHeader (eDNNPTunnelData);
newTunnelMsgs.push_back (newMsg);
m_NumSentBytes += TUNNEL_DATA_MSG_SIZE;
}
m_Buffer.ClearTunnelDataMsgs ();
dotnet::transport::transports.SendMessages (m_Tunnel->GetNextIdentHash (), newTunnelMsgs);
}
}
}

56
libdotnet/TunnelGateway.h Normal file
View file

@ -0,0 +1,56 @@
#ifndef TUNNEL_GATEWAY_H__
#define TUNNEL_GATEWAY_H__
#include <inttypes.h>
#include <vector>
#include <memory>
#include "DNNPProtocol.h"
#include "TunnelBase.h"
namespace dotnet
{
namespace tunnel
{
class TunnelGatewayBuffer
{
public:
TunnelGatewayBuffer ();
~TunnelGatewayBuffer ();
void PutDNNPMsg (const TunnelMessageBlock& block);
const std::vector<std::shared_ptr<const DNNPMessage> >& GetTunnelDataMsgs () const { return m_TunnelDataMsgs; };
void ClearTunnelDataMsgs ();
void CompleteCurrentTunnelDataMessage ();
private:
void CreateCurrentTunnelDataMessage ();
private:
std::vector<std::shared_ptr<const DNNPMessage> > m_TunnelDataMsgs;
std::shared_ptr<DNNPMessage> 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

620
libdotnet/TunnelPool.cpp Normal file
View file

@ -0,0 +1,620 @@
#include <algorithm>
#include "DotNetEndian.h"
#include "Crypto.h"
#include "Tunnel.h"
#include "NetDb.hpp"
#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 dotnet
{
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<dotnet::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 ();
}
bool TunnelPool::Reconfigure(int inHops, int outHops, int inQuant, int outQuant) {
if( inHops >= 0 && outHops >= 0 && inQuant > 0 && outQuant > 0)
{
m_NumInboundHops = inHops;
m_NumOutboundHops = outHops;
m_NumInboundTunnels = inQuant;
m_NumOutboundTunnels = outQuant;
return true;
}
return false;
}
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<DNNPMessage> msg)
{
if (m_LocalDestination)
m_LocalDestination->ProcessGarlicMessage (msg);
else
LogPrint (eLogWarning, "Tunnels: local destination doesn't exist, dropped");
}
void TunnelPool::ProcessDeliveryStatus (std::shared_ptr<DNNPMessage> 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 = dotnet::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 dotnet::data::RouterInfo> TunnelPool::SelectNextHop (std::shared_ptr<const dotnet::data::RouterInfo> prevHop) const
{
bool isExploratory = (dotnet::tunnel::tunnels.GetExploratoryPool () == shared_from_this ());
auto hop = isExploratory ? dotnet::data::netdb.GetRandomRouter (prevHop):
dotnet::data::netdb.GetHighBandwidthRandomRouter (prevHop);
if (!hop || hop->GetProfile ()->IsBad ())
hop = dotnet::data::netdb.GetRandomRouter (prevHop);
return hop;
}
bool StandardSelectPeers(Path & peers, int numHops, bool inbound, SelectHopFunc nextHop)
{
auto prevHop = dotnet::context.GetSharedRouterInfo ();
if(dotnet::transport::transports.RoutesRestricted())
{
/** if routes are restricted prepend trusted first hop */
auto hop = dotnet::transport::transports.GetRestrictedPeer();
if(!hop) return false;
peers.push_back(hop->GetRouterIdentity());
prevHop = hop;
}
else if (dotnet::transport::transports.GetNumPeers () > 25)
{
auto r = dotnet::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 dotnet::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 dotnet::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 = dotnet::data::netdb.FindRouter (ident);
if (r)
peers.push_back (r->GetRouterIdentity ());
else
{
LogPrint (eLogInfo, "Tunnels: Can't find router for ", ident.ToBase64 ());
dotnet::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 dotnet::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 && tunnel->GetPeers().size())
{
config = std::make_shared<TunnelConfig>(tunnel->GetPeers ());
}
if (m_NumInboundHops == 0 || config)
{
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 dotnet::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 && tunnel->GetPeers().size())
{
config = std::make_shared<TunnelConfig>(tunnel->GetPeers (), inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ());
}
if(m_NumOutboundHops == 0 || config)
{
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);
}
}
}

144
libdotnet/TunnelPool.h Normal file
View file

@ -0,0 +1,144 @@
#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 "DNNPProtocol.h"
#include "TunnelBase.h"
#include "RouterContext.h"
#include "Garlic.h"
namespace dotnet
{
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 dotnet::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 dotnet::data::RouterInfo>(std::shared_ptr<const dotnet::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<dotnet::garlic::GarlicDestination> GetLocalDestination () const { return m_LocalDestination; };
void SetLocalDestination (std::shared_ptr<dotnet::garlic::GarlicDestination> destination) { m_LocalDestination = destination; };
void SetExplicitPeers (std::shared_ptr<std::vector<dotnet::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<DNNPMessage> msg);
void ProcessDeliveryStatus (std::shared_ptr<DNNPMessage> 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; };
int GetNumInboundHops() const { return m_NumInboundHops; };
int GetNumOutboundHops() const { return m_NumOutboundHops; };
/** dncp reconfigure */
bool Reconfigure(int inboundHops, int outboundHops, int inboundQuant, int outboundQuant);
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 dotnet::data::RouterInfo> SelectNextHop (std::shared_ptr<const dotnet::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 dotnet::data::IdentityEx> >& hops, bool isInbound);
bool SelectExplicitPeers (std::vector<std::shared_ptr<const dotnet::data::IdentityEx> >& hops, bool isInbound);
private:
std::shared_ptr<dotnet::garlic::GarlicDestination> m_LocalDestination;
int m_NumInboundHops, m_NumOutboundHops, m_NumInboundTunnels, m_NumOutboundTunnels;
std::shared_ptr<std::vector<dotnet::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

136
libdotnet/api.cpp Normal file
View file

@ -0,0 +1,136 @@
#include <string>
#include <map>
#include "Config.h"
#include "Log.h"
#include "NetDb.hpp"
#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 dotnet
{
namespace api
{
void InitDOTNET (int argc, char* argv[], const char * appName)
{
dotnet::config::Init ();
dotnet::config::ParseCmdline (argc, argv, true); // ignore unknown options and help
dotnet::config::Finalize ();
std::string datadir; dotnet::config::GetOption("datadir", datadir);
dotnet::fs::SetAppName (appName);
dotnet::fs::DetectDataDir(datadir, false);
dotnet::fs::Init();
bool precomputation; dotnet::config::GetOption("precomputation.elgamal", precomputation);
dotnet::crypto::InitCrypto (precomputation);
int netID; dotnet::config::GetOption("netid", netID);
dotnet::context.SetNetID (netID);
dotnet::context.Init ();
}
void TerminateDOTNET ()
{
dotnet::crypto::TerminateCrypto ();
}
void StartDOTNET (std::shared_ptr<std::ostream> logStream)
{
if (logStream)
dotnet::log::Logger().SendTo (logStream);
else
dotnet::log::Logger().SendTo (dotnet::fs::DataDirPath (dotnet::fs::GetAppName () + ".log"));
dotnet::log::Logger().Start ();
LogPrint(eLogInfo, "API: starting NetDB");
dotnet::data::netdb.Start();
LogPrint(eLogInfo, "API: starting Transports");
dotnet::transport::transports.Start();
LogPrint(eLogInfo, "API: starting Tunnels");
dotnet::tunnel::tunnels.Start();
}
void StopDOTNET ()
{
LogPrint(eLogInfo, "API: shutting down");
LogPrint(eLogInfo, "API: stopping Tunnels");
dotnet::tunnel::tunnels.Stop();
LogPrint(eLogInfo, "API: stopping Transports");
dotnet::transport::transports.Stop();
LogPrint(eLogInfo, "API: stopping NetDB");
dotnet::data::netdb.Stop();
dotnet::log::Logger().Stop ();
}
void RunPeerTest ()
{
dotnet::transport::transports.PeerTest ();
}
std::shared_ptr<dotnet::client::ClientDestination> CreateLocalDestination (const dotnet::data::PrivateKeys& keys, bool isPublic,
const std::map<std::string, std::string> * params)
{
auto localDestination = std::make_shared<dotnet::client::ClientDestination> (keys, isPublic, params);
localDestination->Start ();
return localDestination;
}
std::shared_ptr<dotnet::client::ClientDestination> CreateLocalDestination (bool isPublic, dotnet::data::SigningKeyType sigType,
const std::map<std::string, std::string> * params)
{
dotnet::data::PrivateKeys keys = dotnet::data::PrivateKeys::CreateRandomKeys (sigType);
auto localDestination = std::make_shared<dotnet::client::ClientDestination> (keys, isPublic, params);
localDestination->Start ();
return localDestination;
}
void DestroyLocalDestination (std::shared_ptr<dotnet::client::ClientDestination> dest)
{
if (dest)
dest->Stop ();
}
void RequestLeaseSet (std::shared_ptr<dotnet::client::ClientDestination> dest, const dotnet::data::IdentHash& remote)
{
if (dest)
dest->RequestDestination (remote);
}
std::shared_ptr<dotnet::stream::Stream> CreateStream (std::shared_ptr<dotnet::client::ClientDestination> dest, const dotnet::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<dotnet::client::ClientDestination> dest, const dotnet::stream::StreamingDestination::Acceptor& acceptor)
{
if (dest)
dest->AcceptStreams (acceptor);
}
void DestroyStream (std::shared_ptr<dotnet::stream::Stream> stream)
{
if (stream)
stream->Close ();
}
}
}

38
libdotnet/api.h Normal file
View 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 dotnet
{
namespace api
{
// initialization start and stop
void InitDOTNET (int argc, char* argv[], const char * appName);
void TerminateDOTNET ();
void StartDOTNET (std::shared_ptr<std::ostream> logStream = nullptr);
// write system log to logStream, if not specified to <appName>.log in application's folder
void StopDOTNET ();
void RunPeerTest (); // should be called after UPnP
// destinations
std::shared_ptr<dotnet::client::ClientDestination> CreateLocalDestination (const dotnet::data::PrivateKeys& keys, bool isPublic = true,
const std::map<std::string, std::string> * params = nullptr);
std::shared_ptr<dotnet::client::ClientDestination> CreateLocalDestination (bool isPublic = false, dotnet::data::SigningKeyType sigType = dotnet::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<dotnet::client::ClientDestination> dest);
// streams
void RequestLeaseSet (std::shared_ptr<dotnet::client::ClientDestination> dest, const dotnet::data::IdentHash& remote);
std::shared_ptr<dotnet::stream::Stream> CreateStream (std::shared_ptr<dotnet::client::ClientDestination> dest, const dotnet::data::IdentHash& remote);
void AcceptStream (std::shared_ptr<dotnet::client::ClientDestination> dest, const dotnet::stream::StreamingDestination::Acceptor& acceptor);
void DestroyStream (std::shared_ptr<dotnet::stream::Stream> stream);
}
}
#endif

345
libdotnet/util.cpp Normal file
View file

@ -0,0 +1,345 @@
#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 <sysinfoapi.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))
// inet_pton exists Windows since Vista, but XP haven't that function!
// This function was written by Petar Korponai?. See http://stackoverflow.com/questions/15660203/inet-pton-identifier-not-found
int inet_pton_xp(int af, const char *src, void *dst)
{
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 dotnet
{
namespace util
{
namespace net
{
#ifdef WIN32
bool IsWindowsXPorLater()
{
OSVERSIONINFO osvi;
ZeroMemory(&osvi, sizeof(OSVERSIONINFO));
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
GetVersionEx(&osvi);
if (osvi.dwMajorVersion <= 5)
return true;
else
return false;
}
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 (IsWindowsXPorLater())
{
#define inet_pton inet_pton_xp
}
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");
if(ipv6)
return boost::asio::ip::address::from_string("::1");
else
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 = "::1";
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
} // dotnet

133
libdotnet/util.h Normal file
View file

@ -0,0 +1,133 @@
#ifndef UTIL_H
#define UTIL_H
#include <string>
#include <functional>
#include <memory>
#include <mutex>
#include <utility>
#include <boost/asio.hpp>
#ifdef ANDROID
#ifndef __clang__
#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
#endif
namespace dotnet
{
namespace util
{
template<class T>
class MemoryPool
{
//BOOST_STATIC_ASSERT_MSG(sizeof(T) >= sizeof(void*), "size cannot be less that general pointer size");
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(std::forward<TArgs>(args)...);
else
{
auto tmp = m_Head;
m_Head = static_cast<T*>(*(void * *)m_Head); // next
return new (tmp)T(std::forward<TArgs>(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 (std::forward<TArgs>(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 (std::forward<TArgs>(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(std::forward<TArgs>(args)...);
std::lock_guard<std::mutex> l(m_Mutex);
return this->Acquire (std::forward<TArgs>(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
libdotnet/version.h Normal file
View file

@ -0,0 +1,28 @@
#ifndef _VERSION_H_
#define _VERSION_H_
#define CODENAME "Opening"
#define STRINGIZE(x) #x
#define MAKE_VERSION(a,b,c) STRINGIZE(a) "." STRINGIZE(b) "." STRINGIZE(c)
#define DOTNET_VERSION_MAJOR 0
#define DOTNET_VERSION_MINOR 1
#define DOTNET_VERSION_MICRO 16
#define DOTNET_VERSION_PATCH 0
#define DOTNET_VERSION MAKE_VERSION(DOTNET_VERSION_MAJOR, DOTNET_VERSION_MINOR, DOTNET_VERSION_MICRO)
#define VERSION DOTNET_VERSION
#ifdef MESHNET
#define DOTNET_NET_ID 3
#else
#define DOTNET_NET_ID 2
#endif
#define DOTNET_VERSION_MAJOR 0
#define DOTNET_VERSION_MINOR 1
#define DOTNET_VERSION_MICRO 16
#define DOTNET_VERSION_PATCH 0
#define DOTNET_VERSION MAKE_VERSION(DOTNET_VERSION_MAJOR, DOTNET_VERSION_MINOR, DOTNET_VERSION_MICRO)
#endif