i2pd/libi2pd/Reseed.cpp
Anatolii Cherednichenko 55534ea002 Reformat code
2022-08-30 02:11:28 +03:00

728 lines
33 KiB
C++

/*
* Copyright (c) 2013-2022, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*/
#include <string.h>
#include <fstream>
#include <sstream>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/algorithm/string.hpp>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <zlib.h>
#include "Crypto.h"
#include "I2PEndian.h"
#include "Reseed.h"
#include "FS.h"
#include "Log.h"
#include "Identity.h"
#include "NetDb.hpp"
#include "HTTP.h"
#include "util.h"
#include "Config.h"
namespace i2p {
namespace data {
Reseeder::Reseeder() {
}
Reseeder::~Reseeder() {
}
/**
@brief tries to bootstrap into I2P network (from local files and servers, with respect of options)
*/
void Reseeder::Bootstrap() {
std::string su3FileName;
i2p::config::GetOption("reseed.file", su3FileName);
std::string zipFileName;
i2p::config::GetOption("reseed.zipfile", zipFileName);
if (su3FileName.length() > 0) // bootstrap from SU3 file or URL
{
int num;
if (su3FileName.length() > 8 && su3FileName.substr(0, 8) == "https://") {
num = ReseedFromSU3Url(su3FileName); // from https URL
} else {
num = ProcessSU3File(su3FileName.c_str());
}
if (num == 0)
LogPrint(eLogWarning, "Reseed: Failed to reseed from ", su3FileName);
} else if (zipFileName.length() > 0) // bootstrap from ZIP file
{
int num = ProcessZIPFile(zipFileName.c_str());
if (num == 0)
LogPrint(eLogWarning, "Reseed: Failed to reseed from ", zipFileName);
} else // bootstrap from reseed servers
{
int num = ReseedFromServers();
if (num == 0)
LogPrint(eLogWarning, "Reseed: Failed to reseed from servers");
}
}
/**
* @brief bootstrap from random server, retry 10 times
* @return number of entries added to netDb
*/
int Reseeder::ReseedFromServers() {
bool ipv6;
i2p::config::GetOption("ipv6", ipv6);
bool ipv4;
i2p::config::GetOption("ipv4", ipv4);
bool yggdrasil;
i2p::config::GetOption("meshnets.yggdrasil", yggdrasil);
std::vector<std::string> httpsReseedHostList;
if (ipv4 || ipv6) {
std::string reseedURLs;
i2p::config::GetOption("reseed.urls", reseedURLs);
if (!reseedURLs.empty())
boost::split(httpsReseedHostList, reseedURLs, boost::is_any_of(","), boost::token_compress_on);
}
std::vector<std::string> yggReseedHostList;
if (yggdrasil && !i2p::util::net::GetYggdrasilAddress().is_unspecified()) {
LogPrint(eLogInfo, "Reseed: Yggdrasil is supported");
std::string yggReseedURLs;
i2p::config::GetOption("reseed.yggurls", yggReseedURLs);
if (!yggReseedURLs.empty())
boost::split(yggReseedHostList, yggReseedURLs, boost::is_any_of(","), boost::token_compress_on);
}
if (httpsReseedHostList.empty() && yggReseedHostList.empty()) {
LogPrint(eLogWarning, "Reseed: No reseed servers specified");
return 0;
}
int reseedRetries = 0;
while (reseedRetries < 10) {
auto ind = rand() % (httpsReseedHostList.size() + yggReseedHostList.size());
bool isHttps = ind < httpsReseedHostList.size();
std::string reseedUrl = isHttps ? httpsReseedHostList[ind] :
yggReseedHostList[ind - httpsReseedHostList.size()];
reseedUrl += "i2pseeds.su3";
auto num = ReseedFromSU3Url(reseedUrl, isHttps);
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, bool isHttps) {
LogPrint(eLogInfo, "Reseed: Downloading SU3 from ", url);
std::string su3 = isHttps ? HttpsRequest(url) : YggdrasilRequest(url);
if (su3.length() > 0) {
std::stringstream s(su3);
return ProcessSU3Stream(s);
} else {
LogPrint(eLogWarning, "Reseed: SU3 download failed");
return 0;
}
}
int Reseeder::ProcessSU3File(const char *filename) {
std::ifstream s(filename, std::ifstream::binary);
if (s.is_open())
return ProcessSU3Stream(s);
else {
LogPrint(eLogError, "Reseed: Can't open file ", filename);
return 0;
}
}
int Reseeder::ProcessZIPFile(const char *filename) {
std::ifstream s(filename, std::ifstream::binary);
if (s.is_open()) {
s.seekg(0, std::ios::end);
auto len = s.tellg();
s.seekg(0, std::ios::beg);
return ProcessZIPStream(s, len);
} else {
LogPrint(eLogError, "Reseed: Can't open file ", filename);
return 0;
}
}
const char SU3_MAGIC_NUMBER[] = "I2Psu3";
int Reseeder::ProcessSU3Stream(std::istream &s) {
char magicNumber[7];
s.read(magicNumber, 7); // magic number and zero byte 6
if (strcmp(magicNumber, SU3_MAGIC_NUMBER)) {
LogPrint(eLogError, "Reseed: Unexpected SU3 magic number");
return 0;
}
s.seekg(1, std::ios::cur); // su3 file format version
SigningKeyType signatureType;
s.read((char *) &signatureType, 2); // signature type
signatureType = be16toh(signatureType);
uint16_t signatureLength;
s.read((char *) &signatureLength, 2); // signature length
signatureLength = be16toh(signatureLength);
s.seekg(1, std::ios::cur); // unused
uint8_t versionLength;
s.read((char *) &versionLength, 1); // version length
s.seekg(1, std::ios::cur); // unused
uint8_t signerIDLength;
s.read((char *) &signerIDLength, 1); // signer ID length
uint64_t contentLength;
s.read((char *) &contentLength, 8); // content length
contentLength = be64toh(contentLength);
s.seekg(1, std::ios::cur); // unused
uint8_t fileType;
s.read((char *) &fileType, 1); // file type
if (fileType != 0x00) // zip file
{
LogPrint(eLogError, "Reseed: Can't handle file type ", (int) fileType);
return 0;
}
s.seekg(1, std::ios::cur); // unused
uint8_t contentType;
s.read((char *) &contentType, 1); // content type
if (contentType != 0x03) // reseed data
{
LogPrint(eLogError, "Reseed: Unexpected content type ", (int) contentType);
return 0;
}
s.seekg(12, std::ios::cur); // unused
s.seekg(versionLength, std::ios::cur); // skip version
char signerID[256];
s.read(signerID, signerIDLength); // signerID
signerID[signerIDLength] = 0;
bool verify;
i2p::config::GetOption("reseed.verify", verify);
if (verify) {
//try to verify signature
auto it = m_SigningKeys.find(signerID);
if (it != m_SigningKeys.end()) {
// TODO: implement all signature types
if (signatureType == SIGNING_KEY_TYPE_RSA_SHA512_4096) {
size_t pos = s.tellg();
size_t tbsLen = pos + contentLength;
uint8_t *tbs = new uint8_t[tbsLen];
s.seekg(0, std::ios::beg);
s.read((char *) tbs, tbsLen);
uint8_t *signature = new uint8_t[signatureLength];
s.read((char *) signature, signatureLength);
// RSA-raw
{
// calculate digest
uint8_t digest[64];
SHA512(tbs, tbsLen, digest);
// encrypt signature
BN_CTX *bnctx = BN_CTX_new();
BIGNUM *s = BN_new(), *n = BN_new();
BN_bin2bn(signature, signatureLength, s);
BN_bin2bn(it->second, 512, n); // RSA 4096 assumed
BN_mod_exp(s, s, i2p::crypto::GetRSAE(), n, bnctx); // s = s^e mod n
uint8_t *enSigBuf = new uint8_t[signatureLength];
i2p::crypto::bn2buf(s, enSigBuf, signatureLength);
// digest is right aligned
// we can't use RSA_verify due wrong padding in SU3
if (memcmp(enSigBuf + (signatureLength - 64), digest, 64))
LogPrint(eLogWarning, "Reseed: SU3 signature verification failed");
else
verify = false; // verified
delete[] enSigBuf;
BN_free(s);
BN_free(n);
BN_CTX_free(bnctx);
}
delete[] signature;
delete[] tbs;
s.seekg(pos, std::ios::beg);
} else
LogPrint(eLogWarning, "Reseed: Signature type ", signatureType, " is not supported");
} else
LogPrint(eLogWarning, "Reseed: Certificate for ", signerID, " not loaded");
}
if (verify) // not verified
{
LogPrint(eLogError, "Reseed: SU3 verification failed");
return 0;
}
// handle content
return ProcessZIPStream(s, contentLength);
}
const uint32_t ZIP_HEADER_SIGNATURE = 0x04034B50;
const uint32_t ZIP_CENTRAL_DIRECTORY_HEADER_SIGNATURE = 0x02014B50;
const uint16_t ZIP_BIT_FLAG_DATA_DESCRIPTOR = 0x0008;
int Reseeder::ProcessZIPStream(std::istream &s, uint64_t contentLength) {
int numFiles = 0;
size_t contentPos = s.tellg();
while (!s.eof()) {
uint32_t signature;
s.read((char *) &signature, 4);
signature = le32toh (signature);
if (signature == ZIP_HEADER_SIGNATURE) {
// next local file
s.seekg(2, std::ios::cur); // version
uint16_t bitFlag;
s.read((char *) &bitFlag, 2);
bitFlag = le16toh (bitFlag);
uint16_t compressionMethod;
s.read((char *) &compressionMethod, 2);
compressionMethod = le16toh (compressionMethod);
s.seekg(4, std::ios::cur); // skip fields we don't care about
uint32_t compressedSize, uncompressedSize;
uint32_t crc_32;
s.read((char *) &crc_32, 4);
crc_32 = le32toh (crc_32);
s.read((char *) &compressedSize, 4);
compressedSize = le32toh (compressedSize);
s.read((char *) &uncompressedSize, 4);
uncompressedSize = le32toh (uncompressedSize);
uint16_t fileNameLength, extraFieldLength;
s.read((char *) &fileNameLength, 2);
fileNameLength = le16toh (fileNameLength);
if (fileNameLength > 255) {
// too big
LogPrint(eLogError, "Reseed: SU3 fileNameLength too large: ", fileNameLength);
return numFiles;
}
s.read((char *) &extraFieldLength, 2);
extraFieldLength = le16toh (extraFieldLength);
char localFileName[255];
s.read(localFileName, fileNameLength);
localFileName[fileNameLength] = 0;
s.seekg(extraFieldLength, std::ios::cur);
// take care about data 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) {
i2p::data::netdb.AddRouterInfo(uncompressed, uncompressedSize);
numFiles++;
} else
LogPrint(eLogError, "Reseed: CRC32 verification failed");
} else
LogPrint(eLogError, "Reseed: SU3 decompression error ", err);
delete[] uncompressed;
inflateEnd(&inflator);
} else // no compression
{
i2p::data::netdb.AddRouterInfo(compressed, compressedSize);
numFiles++;
}
delete[] compressed;
if (bitFlag & ZIP_BIT_FLAG_DATA_DESCRIPTOR)
s.seekg(12, std::ios::cur); // skip data descriptor section if presented (12 = 16 - 4)
} else {
if (signature != ZIP_CENTRAL_DIRECTORY_HEADER_SIGNATURE)
LogPrint(eLogWarning, "Reseed: Missing zip central directory header");
break; // no more files
}
size_t end = s.tellg();
if (end - contentPos >= contentLength)
break; // we are beyond contentLength
}
if (numFiles) // check if routers are not outdated
{
auto ts = i2p::util::GetMillisecondsSinceEpoch();
int numOutdated = 0;
i2p::data::netdb.VisitRouterInfos(
[&numOutdated, ts](std::shared_ptr<const RouterInfo> r) {
if (r && ts > r->GetTimestamp() +
10 * i2p::data::NETDB_MAX_EXPIRATION_TIMEOUT * 1000LL) // 270 hours
{
LogPrint(eLogError, "Reseed: Router ", r->GetIdentHash().ToBase64(), " is outdated by ",
(ts - r->GetTimestamp()) / 1000LL / 3600LL, " hours");
numOutdated++;
}
});
if (numOutdated > numFiles / 2) // more than half
{
LogPrint(eLogError, "Reseed: Mammoth's shit\n"
" *_____*\n"
" *_*****_*\n"
" *_(O)_(O)_*\n"
" **____V____**\n"
" **_________**\n"
" **_________**\n"
" *_________*\n"
" ***___***");
i2p::data::netdb.ClearRouterInfos();
numFiles = 0;
}
}
return numFiles;
}
const uint8_t ZIP_DATA_DESCRIPTOR_SIGNATURE[] = {0x50, 0x4B, 0x07, 0x08};
bool Reseeder::FindZipDataDescriptor(std::istream &s) {
size_t nextInd = 0;
while (!s.eof()) {
uint8_t nextByte;
s.read((char *) &nextByte, 1);
if (nextByte == ZIP_DATA_DESCRIPTOR_SIGNATURE[nextInd]) {
nextInd++;
if (nextInd >= sizeof(ZIP_DATA_DESCRIPTOR_SIGNATURE))
return true;
} else
nextInd = 0;
}
return false;
}
void Reseeder::LoadCertificate(const std::string &filename) {
SSL_CTX *ctx = SSL_CTX_new(TLS_method());
int ret = SSL_CTX_use_certificate_file(ctx, filename.c_str(), SSL_FILETYPE_PEM);
if (ret) {
SSL *ssl = SSL_new(ctx);
X509 *cert = SSL_get_certificate(ssl);
// verify
if (cert) {
// extract issuer name
char name[100];
X509_NAME_oneline(X509_get_issuer_name(cert), name, 100);
char *cn = strstr(name, "CN=");
if (cn) {
cn += 3;
char *terminator = strchr(cn, '/');
if (terminator) terminator[0] = 0;
}
// extract RSA key (we need n only, e = 65537)
const RSA *key = EVP_PKEY_get0_RSA(X509_get_pubkey(cert));
const BIGNUM *n, *e, *d;
RSA_get0_key(key, &n, &e, &d);
PublicKey value;
i2p::crypto::bn2buf(n, value, 512);
if (cn)
m_SigningKeys[cn] = value;
else
LogPrint(eLogError, "Reseed: Can't find CN field in ", filename);
}
SSL_free(ssl);
} else
LogPrint(eLogError, "Reseed: Can't open certificate file ", filename);
SSL_CTX_free(ctx);
}
void Reseeder::LoadCertificates() {
std::string certDir = i2p::fs::GetCertsDir() + i2p::fs::dirSep + "reseed";
std::vector<std::string> files;
int numCertificates = 0;
if (!i2p::fs::ReadDir(certDir, files)) {
LogPrint(eLogWarning, "Reseed: Can't load reseed certificates from ", certDir);
return;
}
for (const std::string &file: files) {
if (file.compare(file.size() - 4, 4, ".crt") != 0) {
LogPrint(eLogWarning, "Reseed: Ignoring file ", file);
continue;
}
LoadCertificate(file);
numCertificates++;
}
LogPrint(eLogInfo, "Reseed: ", numCertificates, " certificates loaded");
}
std::string Reseeder::HttpsRequest(const std::string &address) {
i2p::http::URL proxyUrl;
std::string proxy;
i2p::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 "";
}
}
i2p::http::URL url;
if (!url.parse(address)) {
LogPrint(eLogError, "Reseed: Failed to parse url: ", address);
return "";
}
url.schema = "https";
if (!url.port)
url.port = 443;
boost::asio::io_service service;
boost::system::error_code ecode;
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") {
i2p::http::HTTPReq proxyReq;
i2p::http::HTTPRes proxyRes;
proxyReq.method = "CONNECT";
proxyReq.version = "HTTP/1.1";
proxyReq.uri = url.host + ":" + std::to_string(url.port);
auto auth = i2p::http::CreateBasicAuthorizationString(proxyUrl.user, proxyUrl.pass);
if (!auth.empty())
proxyReq.AddHeader("Proxy-Authorization", auth);
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) {
bool connected = false;
boost::asio::ip::tcp::resolver::iterator end;
while (it != end) {
boost::asio::ip::tcp::endpoint ep = *it;
if ((ep.address().is_v4() && i2p::context.SupportsV4()) ||
(ep.address().is_v6() && i2p::context.SupportsV6())) {
s.lowest_layer().connect(ep, ecode);
if (!ecode) {
connected = true;
break;
}
}
it++;
}
if (!connected) {
LogPrint(eLogError, "Reseed: Failed to connect to ", url.host);
return "";
}
}
}
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);
return ReseedRequest(s, url.to_string());
} else
LogPrint(eLogError, "Reseed: SSL handshake failed: ", ecode.message());
} else
LogPrint(eLogError, "Reseed: Couldn't connect to ", url.host, ": ", ecode.message());
return "";
}
template<typename Stream>
std::string Reseeder::ReseedRequest(Stream &s, const std::string &uri) {
boost::system::error_code ecode;
i2p::http::HTTPReq req;
req.uri = uri;
req.AddHeader("User-Agent", "Wget/1.11.4");
req.AddHeader("Connection", "close");
s.write_some(boost::asio::buffer(req.to_string()));
// read response
std::stringstream rs;
char recv_buf[1024];
size_t l = 0;
do {
l = s.read_some(boost::asio::buffer(recv_buf, sizeof(recv_buf)), ecode);
if (l) rs.write(recv_buf, l);
} while (!ecode && l);
// process response
std::string data = rs.str();
i2p::http::HTTPRes res;
int len = res.parse(data);
if (len <= 0) {
LogPrint(eLogWarning, "Reseed: Incomplete/broken response from ", uri);
return "";
}
if (res.code != 200) {
LogPrint(eLogError, "Reseed: Failed to reseed from ", uri, ", http code ", res.code);
return "";
}
data.erase(0, len); /* drop http headers from response */
LogPrint(eLogDebug, "Reseed: Got ", data.length(), " bytes of data from ", uri);
if (res.is_chunked()) {
std::stringstream in(data), out;
if (!i2p::http::MergeChunkedResponse(in, out)) {
LogPrint(eLogWarning, "Reseed: Failed to merge chunked response from ", uri);
return "";
}
LogPrint(eLogDebug, "Reseed: Got ", data.length(), "(", out.tellg(), ") bytes of data from ", uri);
data = out.str();
}
return data;
}
std::string Reseeder::YggdrasilRequest(const std::string &address) {
i2p::http::URL url;
if (!url.parse(address)) {
LogPrint(eLogError, "Reseed: Failed to parse url: ", address);
return "";
}
url.schema = "http";
if (!url.port) url.port = 80;
boost::system::error_code ecode;
boost::asio::io_service service;
boost::asio::ip::tcp::socket s(service, boost::asio::ip::tcp::v6());
if (url.host.length() < 2) return ""; // assume []
auto host = url.host.substr(1, url.host.length() - 2);
LogPrint(eLogDebug, "Reseed: Connecting to Yggdrasil ", url.host, ":", url.port);
s.connect(boost::asio::ip::tcp::endpoint(boost::asio::ip::address_v6::from_string(host), url.port), ecode);
if (!ecode) {
LogPrint(eLogDebug, "Reseed: Connected to Yggdrasil ", url.host, ":", url.port);
return ReseedRequest(s, url.to_string());
} else
LogPrint(eLogError, "Reseed: Couldn't connect to Yggdrasil ", url.host, ": ", ecode.message());
return "";
}
}
}