mirror of
https://github.com/RetroShare/libretroshare.git
synced 2026-09-12 19:50:03 +05:00
stopSession() ended with an unconditional 10 s sleep, meant to give i2pd
time to let go of a session before the next one is created ("restart"
button in the settings, which posts a stop ticket immediately followed by
a start ticket).
A stop is not always followed by a start. On application exit
p3face-config's shutdown sequence calls rsAutoProxyMonitor::stopAllRSShutdown(),
which posts that same stop ticket and then waits for the service to
acknowledge it; the sleep therefore delayed the whole shutdown by 10 s
for nothing. Measured on a node with SAM3 enabled: 10.0 s of a 21.8 s
shutdown were spent in this sleep, the monitor logging
"waiting for auto proxy service(s) to shut down 0..10/15 (remaining: 1)".
The "stop" button in the settings pays it too, equally uselessly.
Record when the session was closed instead, and wait out what is left of
the delay in startSession(), where it is actually needed. The guarantee
i2pd relies on is unchanged -- a session is still never created less than
10 s after the previous one was closed -- but a stop that is not followed
by a start now returns immediately.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
820 lines
23 KiB
C++
820 lines
23 KiB
C++
#include "p3i2psam3.h"
|
|
|
|
#include <libsam3.h>
|
|
|
|
#include "pqi/p3peermgr.h"
|
|
#include "rsitems/rsconfigitems.h"
|
|
|
|
|
|
static const std::string kConfigKeySAM3Enable = "SAM3_ENABLE";
|
|
|
|
static const std::string kConfigKeyDestPriv = "DEST_PRIV";
|
|
|
|
static const std::string kConfigKeyInLength = "IN_LENGTH";
|
|
static const std::string kConfigKeyInQuantity = "IN_QUANTITY";
|
|
static const std::string kConfigKeyInVariance = "IN_VARIANCE";
|
|
static const std::string kConfigKeyInBackupQuantity = "IN_BACKUPQUANTITY";
|
|
|
|
static const std::string kConfigKeyOutLength = "OUT_LENGTH";
|
|
static const std::string kConfigKeyOutQuantity = "OUT_QUANTITY";
|
|
static const std::string kConfigKeyOutVariance = "OUT_VARIANCE";
|
|
static const std::string kConfigKeyOutBackupQuantity = "OUT_BACKUPQUANTITY";
|
|
|
|
#ifdef RS_I2P_SAM3_BOB_COMPAT
|
|
// used for migration from BOB to SAM
|
|
static const std::string kConfigKeyBOBEnable = "BOB_ENABLE";
|
|
static const std::string kConfigKeyBOBKey = "BOB_KEY";
|
|
static const std::string kConfigKeyBOBAddr = "BOB_ADDR";
|
|
#endif
|
|
|
|
static constexpr bool kDefaultSAM3Enable = false;
|
|
|
|
/* At least i2pd doesn't like to instantaniously stop and (re)start a session, so
|
|
* a new session is never created less than this long after the previous one was
|
|
* closed. The wait happens in startSession(), where it is actually needed, and
|
|
* not in stopSession(): a stop is not always followed by a start (RS shutdown,
|
|
* "stop" button in the settings) and waiting there held back the whole
|
|
* application exit by 10 seconds. */
|
|
static constexpr auto kSessionRestartDelay = std::chrono::seconds(10);
|
|
|
|
RS_SET_CONTEXT_DEBUG_LEVEL(2)
|
|
|
|
static void inline doSleep(std::chrono::duration<long, std::ratio<1,1000>> timeToSleepMS) {
|
|
std::this_thread::sleep_for(timeToSleepMS);
|
|
}
|
|
|
|
p3I2pSam3::p3I2pSam3(p3PeerMgr *peerMgr) :
|
|
mConfigLoaded(false), mPeerMgr(peerMgr), mPending(),
|
|
mLastSessionClosed(std::chrono::steady_clock::time_point::min()),
|
|
mLock("p3i2p-sam3")
|
|
#ifdef RS_USE_I2P_SAM3_LIBSAM3
|
|
, mLockSam3Access("p3i2p-sam3-access")
|
|
#endif
|
|
{
|
|
RS_DBG4();
|
|
|
|
// set defaults
|
|
mSetting.initDefault();
|
|
mSetting.enable = kDefaultSAM3Enable;
|
|
mSetting.session = nullptr;
|
|
|
|
libsam3_debug = 0;
|
|
}
|
|
|
|
bool p3I2pSam3::isEnabled()
|
|
{
|
|
RS_STACK_MUTEX(mLock);
|
|
return mSetting.enable;
|
|
}
|
|
|
|
bool p3I2pSam3::initialSetup(std::string &addr, uint16_t &/*port*/)
|
|
{
|
|
RS_DBG4();
|
|
|
|
RS_STACK_MUTEX(mLock);
|
|
|
|
if (!mSetting.address.publicKey.empty() || !mSetting.address.privateKey.empty())
|
|
RS_WARN("overwriting keys!");
|
|
|
|
bool success = generateKey(mSetting.address.publicKey, mSetting.address.privateKey);
|
|
|
|
if (!success) {
|
|
RS_WARN("failed to retrieve keys");
|
|
return false;
|
|
} else {
|
|
std::string s, c;
|
|
i2p::getKeyTypes(mSetting.address.publicKey, s, c);
|
|
RS_INFO("received key ", s, " ", c);
|
|
RS_INFO("public key: ", mSetting.address.publicKey);
|
|
RS_INFO("private key: ", mSetting.address.privateKey);
|
|
RS_INFO("address: ", i2p::keyToBase32Addr(mSetting.address.publicKey));
|
|
|
|
// sanity check
|
|
auto pub = i2p::publicKeyFromPrivate(mSetting.address.privateKey);
|
|
RS_INFO("pub key derived: ", pub);
|
|
RS_INFO("address: ", i2p::keyToBase32Addr(pub));
|
|
if (pub != mSetting.address.publicKey) {
|
|
RS_WARN("public key does not match private key! fixing ...");
|
|
mSetting.address.publicKey = pub;
|
|
}
|
|
|
|
mSetting.address.base32 = i2p::keyToBase32Addr(mSetting.address.publicKey);
|
|
|
|
IndicateConfigChanged();
|
|
}
|
|
|
|
addr = mSetting.address.base32;
|
|
return true;
|
|
}
|
|
|
|
void p3I2pSam3::processTaskAsync(taskTicket *ticket)
|
|
{
|
|
RS_DBG4();
|
|
|
|
switch (ticket->task) {
|
|
case autoProxyTask::stop: [[fallthrough]];
|
|
case autoProxyTask::start: [[fallthrough]];
|
|
case autoProxyTask::receiveKey: [[fallthrough]];
|
|
case autoProxyTask::lookupKey: [[fallthrough]];
|
|
case autoProxyTask::proxyStatusCheck: [[fallthrough]];
|
|
case autoProxyTask::establishConnection: [[fallthrough]];
|
|
case autoProxyTask::closeConnection:
|
|
{
|
|
RS_STACK_MUTEX(mLock);
|
|
mPending.push(ticket);
|
|
}
|
|
break;
|
|
case autoProxyTask::status: [[fallthrough]];
|
|
case autoProxyTask::getSettings: [[fallthrough]];
|
|
case autoProxyTask::setSettings: [[fallthrough]];
|
|
case autoProxyTask::getErrorInfo: [[fallthrough]];
|
|
case autoProxyTask::reloadConfig:
|
|
// These are supposed to be sync!
|
|
RS_DBG("unknown task or sync one!");
|
|
rsAutoProxyMonitor::taskError(ticket);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void p3I2pSam3::processTaskSync(taskTicket *ticket)
|
|
{
|
|
// RS_DBG4();
|
|
|
|
const bool data = !!ticket->data;
|
|
|
|
switch (ticket->task) {
|
|
case autoProxyTask::status:
|
|
{
|
|
samStatus *ss = static_cast<struct samStatus *>(ticket->data);
|
|
RS_STACK_MUTEX(mLock);
|
|
ss->state = mState;
|
|
if (mSetting.session)
|
|
ss->sessionName = mSetting.session->channel;
|
|
else
|
|
ss->sessionName = "none";
|
|
}
|
|
rsAutoProxyMonitor::taskDone(ticket, autoProxyStatus::ok);
|
|
|
|
break;
|
|
case autoProxyTask::getSettings:
|
|
// check if everything needed is set
|
|
if (!data) {
|
|
RS_DBG("autoProxyTask::getSettings data is missing");
|
|
rsAutoProxyMonitor::taskError(ticket);
|
|
break;
|
|
}
|
|
|
|
// get settings
|
|
{
|
|
RS_STACK_MUTEX(mLock);
|
|
*static_cast<struct samSettings *>(ticket->data) = mSetting;
|
|
}
|
|
|
|
// finish task
|
|
rsAutoProxyMonitor::taskDone(ticket, autoProxyStatus::ok);
|
|
break;
|
|
case autoProxyTask::setSettings:
|
|
// check if everything needed is set
|
|
if (!data) {
|
|
RS_DBG("autoProxyTask::setSettings data is missing");
|
|
rsAutoProxyMonitor::taskError(ticket);
|
|
break;
|
|
}
|
|
|
|
// set settings
|
|
{
|
|
RS_STACK_MUTEX(mLock);
|
|
mSetting = *static_cast<struct samSettings *>(ticket->data);
|
|
updateSettings_locked();
|
|
}
|
|
|
|
// finish task
|
|
rsAutoProxyMonitor::taskDone(ticket, autoProxyStatus::ok);
|
|
break;
|
|
case autoProxyTask::getErrorInfo:
|
|
*static_cast<std::string *>(ticket->data) = mSetting.session->error;
|
|
rsAutoProxyMonitor::taskDone(ticket, autoProxyStatus::ok);
|
|
break;
|
|
case autoProxyTask::reloadConfig:
|
|
{
|
|
RS_STACK_MUTEX(mLock);
|
|
updateSettings_locked();
|
|
}
|
|
rsAutoProxyMonitor::taskDone(ticket, autoProxyStatus::ok);
|
|
break;
|
|
case autoProxyTask::stop:
|
|
#if 0 // doesn't seem to work, socket stays "CLOSE_WAIT"
|
|
// there can be a case where libsam3 will block forever because for some reason it does not detect that the socket it has is dead
|
|
// as a workaroung kill it from here
|
|
if (mState == samStatus::samState::connectSession || mState == samStatus::samState::connectForward) {
|
|
// lock should be held by the main thread
|
|
if (!mTmpSession) {
|
|
// now it's getting weird
|
|
RS_WARN("session is nullptr but mState says it is connecting.");
|
|
// no break! just ignore for now ...
|
|
} else {
|
|
// just close it from here, libsam3 is not thread safe.
|
|
// a bit of a hack but should do the trick
|
|
// sam3CloseSession(mSetting.session);
|
|
sam3tcpDisconnect(mTmpSession->fd);
|
|
// no break! continue as usual to keep everything in line
|
|
}
|
|
}
|
|
#endif
|
|
[[fallthrough]];
|
|
case autoProxyTask::start: [[fallthrough]];
|
|
case autoProxyTask::receiveKey: [[fallthrough]];
|
|
case autoProxyTask::lookupKey: [[fallthrough]];
|
|
case autoProxyTask::proxyStatusCheck: [[fallthrough]];
|
|
case autoProxyTask::establishConnection: [[fallthrough]];
|
|
case autoProxyTask::closeConnection:
|
|
// These are supposed to be async!
|
|
RS_WARN("unknown task or async one!");
|
|
rsAutoProxyMonitor::taskError(ticket);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void p3I2pSam3::threadTick()
|
|
{
|
|
// {
|
|
// RS_STACK_MUTEX(mLock);
|
|
// Dbg4() << __PRETTY_FUNCTION__ << " mPending: " << mPending.size() << std::endl;
|
|
// }
|
|
|
|
if(mPending.empty()) {
|
|
// sleep outisde of lock!
|
|
doSleep(std::chrono::milliseconds(250));
|
|
return;
|
|
}
|
|
|
|
// get task
|
|
taskTicket *tt = nullptr;
|
|
{
|
|
RS_STACK_MUTEX(mLock);
|
|
tt = mPending.front();
|
|
mPending.pop();
|
|
}
|
|
|
|
switch (tt->task) {
|
|
case autoProxyTask::stop:
|
|
mState = samStatus::samState::offline;
|
|
stopForwarding();
|
|
stopSession();
|
|
rsAutoProxyMonitor::taskDone(tt, autoProxyStatus::offline);
|
|
break;
|
|
|
|
case autoProxyTask::start:
|
|
{
|
|
if (!mSetting.enable) {
|
|
rsAutoProxyMonitor::taskDone(tt, autoProxyStatus::disabled);
|
|
break;
|
|
}
|
|
|
|
// create main session
|
|
mState = samStatus::samState::connectSession;
|
|
bool ret = startSession();
|
|
if (!ret) {
|
|
mState = samStatus::samState::offline;
|
|
|
|
rsAutoProxyMonitor::taskError(tt);
|
|
break;
|
|
}
|
|
|
|
// start forwarding
|
|
mState = samStatus::samState::connectForward;
|
|
ret = startForwarding();
|
|
|
|
// finish ticket
|
|
if (ret) {
|
|
mState = samStatus::samState::online;
|
|
rsAutoProxyMonitor::taskDone(tt, autoProxyStatus::online);
|
|
} else {
|
|
mState = samStatus::samState::offline;
|
|
rsAutoProxyMonitor::taskError(tt);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case autoProxyTask::receiveKey:
|
|
{
|
|
i2p::address *addr = static_cast<i2p::address *>(tt->data);
|
|
if (generateKey(addr->publicKey, addr->privateKey)) {
|
|
addr->base32 = i2p::keyToBase32Addr(addr->publicKey);
|
|
rsAutoProxyMonitor::taskDone(tt, autoProxyStatus::ok);
|
|
} else {
|
|
rsAutoProxyMonitor::taskError(tt);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case autoProxyTask::lookupKey:
|
|
lookupKey(tt);
|
|
break;
|
|
|
|
case autoProxyTask::proxyStatusCheck:
|
|
{
|
|
// TODO better detection of status
|
|
bool ok;
|
|
ok = !!mSetting.session->fd;
|
|
ok &= !!mSetting.session->fwd_fd;
|
|
*static_cast<bool*>(tt->data) = ok;
|
|
rsAutoProxyMonitor::taskDone(tt, ok ? autoProxyStatus::ok : autoProxyStatus::error);
|
|
}
|
|
break;
|
|
|
|
case autoProxyTask::establishConnection:
|
|
establishConnection(tt);
|
|
break;
|
|
case autoProxyTask::closeConnection:
|
|
closeConnection(tt);
|
|
break;
|
|
case autoProxyTask::status: [[fallthrough]];
|
|
case autoProxyTask::getSettings: [[fallthrough]];
|
|
case autoProxyTask::setSettings: [[fallthrough]];
|
|
case autoProxyTask::getErrorInfo: [[fallthrough]];
|
|
case autoProxyTask::reloadConfig:
|
|
RS_ERR("unable to handle! This is a bug! task:", tt->task);
|
|
rsAutoProxyMonitor::taskError(tt);
|
|
break;
|
|
}
|
|
tt = nullptr;
|
|
|
|
// give i2p backend some time
|
|
doSleep(std::chrono::milliseconds(100));
|
|
}
|
|
|
|
RsSerialiser *p3I2pSam3::setupSerialiser()
|
|
{
|
|
RsSerialiser* rsSerialiser = new RsSerialiser();
|
|
rsSerialiser->addSerialType(new RsGeneralConfigSerialiser());
|
|
|
|
return rsSerialiser;
|
|
}
|
|
|
|
#define addKVS(_key, _value) \
|
|
kv.key = _key;\
|
|
kv.value = _value;\
|
|
vitem->tlvkvs.pairs.push_back(kv);
|
|
|
|
#define addKVSInt(_key, _value) \
|
|
kv.key = _key;\
|
|
rs_sprintf(kv.value, "%d", _value);\
|
|
vitem->tlvkvs.pairs.push_back(kv);
|
|
|
|
bool p3I2pSam3::saveList(bool &cleanup, std::list<RsItem *> &lst)
|
|
{
|
|
RS_DBG4();
|
|
|
|
cleanup = true;
|
|
RsConfigKeyValueSet *vitem = new RsConfigKeyValueSet;
|
|
RsTlvKeyValue kv;
|
|
|
|
RS_STACK_MUTEX(mLock);
|
|
addKVS(kConfigKeySAM3Enable, mSetting.enable ? "TRUE" : "FALSE")
|
|
addKVS(kConfigKeyDestPriv, mSetting.address.privateKey);
|
|
|
|
addKVSInt(kConfigKeyInLength, mSetting.inLength)
|
|
addKVSInt(kConfigKeyInQuantity, mSetting.inQuantity)
|
|
addKVSInt(kConfigKeyInVariance, mSetting.inVariance)
|
|
addKVSInt(kConfigKeyInBackupQuantity, mSetting.inBackupQuantity)
|
|
|
|
addKVSInt(kConfigKeyOutLength, mSetting.outLength)
|
|
addKVSInt(kConfigKeyOutQuantity, mSetting.outQuantity)
|
|
addKVSInt(kConfigKeyOutVariance, mSetting.outVariance)
|
|
addKVSInt(kConfigKeyOutBackupQuantity, mSetting.outBackupQuantity)
|
|
|
|
#ifdef RS_I2P_SAM3_BOB_COMPAT
|
|
// these allow SAMv3 users to switch back to BOB
|
|
// remove after some time
|
|
addKVS(kConfigKeyBOBEnable, mSetting.enable ? "TRUE" : "FALSE")
|
|
addKVS(kConfigKeyBOBKey, mSetting.address.privateKey)
|
|
addKVS(kConfigKeyBOBAddr, mSetting.address.base32)
|
|
#endif
|
|
lst.push_back(vitem);
|
|
return true;
|
|
}
|
|
|
|
#undef addKVS
|
|
#undef addKVSUInt
|
|
|
|
#define getKVSUInt(_kit, _key, _value) \
|
|
else if (_kit->key == _key) {\
|
|
std::istringstream is(_kit->value);\
|
|
int tmp;\
|
|
is >> tmp;\
|
|
_value = (int8_t)tmp;\
|
|
}
|
|
|
|
bool p3I2pSam3::loadList(std::list<RsItem *> &load)
|
|
{
|
|
RS_DBG4();
|
|
|
|
std::string priv;
|
|
priv.clear();
|
|
|
|
for(std::list<RsItem*>::const_iterator it = load.begin(); it!=load.end(); ++it) {
|
|
RsConfigKeyValueSet *vitem = dynamic_cast<RsConfigKeyValueSet*>(*it);
|
|
if(vitem != NULL) {
|
|
RS_STACK_MUTEX(mLock);
|
|
for(std::list<RsTlvKeyValue>::const_iterator kit = vitem->tlvkvs.pairs.begin(); kit != vitem->tlvkvs.pairs.end(); ++kit) {
|
|
if (kit->key == kConfigKeySAM3Enable)
|
|
mSetting.enable = kit->value == "TRUE";
|
|
else if (kit->key == kConfigKeyDestPriv)
|
|
priv = kit->value;
|
|
getKVSUInt(kit, kConfigKeyInLength, mSetting.inLength)
|
|
getKVSUInt(kit, kConfigKeyInQuantity, mSetting.inQuantity)
|
|
getKVSUInt(kit, kConfigKeyInVariance, mSetting.inVariance)
|
|
getKVSUInt(kit, kConfigKeyInBackupQuantity, mSetting.inBackupQuantity)
|
|
|
|
getKVSUInt(kit, kConfigKeyOutLength, mSetting.outLength)
|
|
getKVSUInt(kit, kConfigKeyOutQuantity, mSetting.outQuantity)
|
|
getKVSUInt(kit, kConfigKeyOutVariance, mSetting.outVariance)
|
|
getKVSUInt(kit, kConfigKeyOutBackupQuantity, mSetting.outBackupQuantity)
|
|
|
|
#ifdef RS_I2P_SAM3_BOB_COMPAT
|
|
// import BOB settings
|
|
else if (kit->key == kConfigKeyBOBEnable)
|
|
mSetting.enable = kit->value == "TRUE";
|
|
else if (kit->key == kConfigKeyBOBKey) {
|
|
// don't overwirte, just import when not set already!
|
|
if (priv.empty())
|
|
priv = kit->value;
|
|
}
|
|
#endif
|
|
else
|
|
RS_INFO("unknown key:", kit->key);
|
|
}
|
|
}
|
|
delete vitem;
|
|
}
|
|
|
|
// get the pub key
|
|
std::string pub = i2p::publicKeyFromPrivate(priv);
|
|
if (pub.empty() || priv.empty())
|
|
RS_DBG("no destination to load");
|
|
else {
|
|
RS_STACK_MUTEX(mLock);
|
|
|
|
mSetting.address.publicKey = pub;
|
|
mSetting.address.privateKey = priv;
|
|
mSetting.address.base32 = i2p::keyToBase32Addr(pub);
|
|
}
|
|
|
|
RS_STACK_MUTEX(mLock);
|
|
mConfigLoaded = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
#undef getKVSUInt
|
|
|
|
bool p3I2pSam3::startSession()
|
|
{
|
|
RS_DBG4();
|
|
|
|
constexpr size_t len = 8;
|
|
const std::string location = RsRandom::alphaNumeric(len);
|
|
const std::string nick = "RetroShare-" + location;
|
|
|
|
std::vector<std::string> params;
|
|
{
|
|
RS_STACK_MUTEX(mLock);
|
|
|
|
// length
|
|
params.push_back(i2p::makeOption("inbound.length", mSetting.inLength));
|
|
params.push_back(i2p::makeOption("outbound.length", mSetting.outLength));
|
|
// variance
|
|
params.push_back(i2p::makeOption("inbound.lengthVariance", + mSetting.inVariance));
|
|
params.push_back(i2p::makeOption("outbound.lengthVariance", + mSetting.outVariance));
|
|
// quantity
|
|
params.push_back(i2p::makeOption("inbound.quantity", + mSetting.inQuantity));
|
|
params.push_back(i2p::makeOption("outbound.quantity", + mSetting.outQuantity));
|
|
// backup quantity
|
|
params.push_back(i2p::makeOption("inbound.backupQuantity", + mSetting.inBackupQuantity));
|
|
params.push_back(i2p::makeOption("outbound.backupQuantity", + mSetting.outBackupQuantity));
|
|
// lease set type (support encrypted one, too)
|
|
params.push_back("i2cp.leaseSetEncType=0,4");
|
|
}
|
|
|
|
std::string paramsStr;
|
|
for (auto &&p : params)
|
|
paramsStr.append(p + " ");
|
|
// keep trailing space for easier extending when necessary
|
|
|
|
int ret;
|
|
|
|
if (mSetting.session) {
|
|
stopSession();
|
|
}
|
|
|
|
// Give i2pd time to let go of the previous session. No lock may be held here.
|
|
std::chrono::steady_clock::time_point sessionReady;
|
|
{
|
|
RS_STACK_MUTEX(mLock);
|
|
sessionReady = mLastSessionClosed + kSessionRestartDelay;
|
|
}
|
|
const auto now = std::chrono::steady_clock::now();
|
|
if (now < sessionReady) {
|
|
const auto wait = std::chrono::duration_cast<std::chrono::milliseconds>(sessionReady - now);
|
|
RS_DBG("waiting ", wait.count(), " ms before creating a new SAM session");
|
|
doSleep(wait);
|
|
}
|
|
|
|
auto session = (Sam3Session*)rs_malloc(sizeof (Sam3Session));
|
|
|
|
// add nick
|
|
paramsStr.append("inbound.nickname=" + nick); // leading space is already there
|
|
|
|
{
|
|
RS_STACK_MUTEX(mLockSam3Access);
|
|
|
|
if(!mSetting.address.privateKey.empty()) {
|
|
RS_DBG3("with destination");
|
|
ret = sam3CreateSession(session, SAM3_HOST_DEFAULT, SAM3_PORT_DEFAULT, mSetting.address.privateKey.c_str(), Sam3SessionType::SAM3_SESSION_STREAM, Sam3SigType::EdDSA_SHA512_Ed25519, paramsStr.c_str());
|
|
} else {
|
|
RS_DBG("without destination");
|
|
ret = sam3CreateSession(session, SAM3_HOST_DEFAULT, SAM3_PORT_DEFAULT, SAM3_DESTINATION_TRANSIENT, Sam3SessionType::SAM3_SESSION_STREAM, Sam3SigType::EdDSA_SHA512_Ed25519, paramsStr.c_str());
|
|
}
|
|
}
|
|
|
|
if (ret != 0) {
|
|
free(session);
|
|
session = nullptr;
|
|
return false;
|
|
}
|
|
|
|
#if 0 // this check is useless. For non i2p hidden locations the public key is temporal anyway and for i2p hidden ones, it is part of the (fixed) private key.
|
|
if (!mSetting.address.publicKey.empty() && mSetting.address.publicKey != session->pubkey)
|
|
// This should be ok for non hidden locations. This should be a problem for hidden i2p locations...
|
|
RS_DBG("public key changed! Yet unsure if this is ok or a problem. Should be fine for non i2p hidden locations or clear net.");
|
|
#endif
|
|
/*
|
|
* Note: sam3CreateSession will issue a name looup of "ME" to receive its public key, thus it is always correct.
|
|
* No need to use i2p::publicKeyFromPrivate()
|
|
*/
|
|
RS_STACK_MUTEX(mLock);
|
|
mSetting.session = session;
|
|
mSetting.address.publicKey = session->pubkey;
|
|
mSetting.address.base32 = i2p::keyToBase32Addr(session->pubkey);
|
|
// do not overwrite the private key, if any!!
|
|
|
|
RS_DBG1("nick: ", nick, " address: ", mSetting.address.base32);
|
|
RS_DBG2(" myDestination.pub ", mSetting.address.publicKey);
|
|
RS_DBG2(" myDestination.priv ", mSetting.address.privateKey);
|
|
return true;
|
|
}
|
|
|
|
bool p3I2pSam3::startForwarding()
|
|
{
|
|
RS_DBG4();
|
|
|
|
if(mSetting.address.privateKey.empty()) {
|
|
RS_DBG3("no private key set");
|
|
// IMPORANT: return true here!
|
|
// since there is no forward session for non hidden nodes, this funtion is successfull by doing nothing
|
|
return true;
|
|
}
|
|
|
|
if (!mSetting.session) {
|
|
RS_WARN("no session found!");
|
|
return false;
|
|
}
|
|
|
|
peerState ps;
|
|
mPeerMgr->getOwnNetStatus(ps);
|
|
|
|
RS_STACK_MUTEX(mLockSam3Access);
|
|
|
|
mSetting.session->silent = true;
|
|
int ret = sam3StreamForward(mSetting.session, sockaddr_storage_iptostring(ps.localaddr).c_str(), sockaddr_storage_port(ps.localaddr));
|
|
if (ret < 0) {
|
|
RS_DBG("forward failed, due to", mSetting.session->error);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void p3I2pSam3::stopSession()
|
|
{
|
|
RS_DBG4();
|
|
|
|
{
|
|
RS_STACK_MUTEX(mLock);
|
|
if (!mSetting.session)
|
|
return;
|
|
|
|
// swap connections
|
|
mInvalidConnections = mValidConnections;
|
|
mValidConnections.clear();
|
|
|
|
RS_STACK_MUTEX(mLockSam3Access);
|
|
sam3CloseSession(mSetting.session);
|
|
free(mSetting.session);
|
|
|
|
mSetting.session = nullptr;
|
|
mState = samStatus::samState::offline;
|
|
|
|
// startSession() waits out kSessionRestartDelay counted from here
|
|
mLastSessionClosed = std::chrono::steady_clock::now();
|
|
}
|
|
}
|
|
|
|
void p3I2pSam3::stopForwarding()
|
|
{
|
|
// nothing to do here, forwarding is stop when closing the seassion
|
|
}
|
|
|
|
bool p3I2pSam3::generateKey(std::string &pub, std::string &priv)
|
|
{
|
|
RS_DBG4();
|
|
|
|
pub.clear();
|
|
priv.clear();
|
|
|
|
// The session is only usef for transporting the data
|
|
Sam3Session ss;
|
|
|
|
if (0 > sam3GenerateKeys(&ss, SAM3_HOST_DEFAULT, SAM3_PORT_DEFAULT, Sam3SigType::EdDSA_SHA512_Ed25519)) {
|
|
RS_DBG("got error: ", ss.error);
|
|
return false;
|
|
}
|
|
|
|
pub = std::string(ss.pubkey);
|
|
priv = std::string(ss.privkey);
|
|
|
|
// sanity check
|
|
auto p = i2p::publicKeyFromPrivate(priv);
|
|
if (p != pub) {
|
|
RS_WARN("public key does not match private key! fixing ...");
|
|
pub = p;
|
|
}
|
|
|
|
RS_DBG2("publuc key / address ", pub);
|
|
RS_DBG2("private key ", priv);
|
|
|
|
return true;
|
|
}
|
|
|
|
void p3I2pSam3::lookupKey(taskTicket *ticket)
|
|
{
|
|
// this can be called independend of the main SAM session!
|
|
|
|
auto addr = static_cast<i2p::address*>(ticket->data);
|
|
if (addr->base32.empty()) {
|
|
RS_ERR("lookupKey: called with empty address");
|
|
rsAutoProxyMonitor::taskError(ticket);
|
|
return;
|
|
}
|
|
|
|
RsThread::async([ticket]()
|
|
{
|
|
auto addr = static_cast<i2p::address*>(ticket->data);
|
|
|
|
// The session is only usef for transporting the data
|
|
Sam3Session ss;
|
|
int ret = sam3NameLookup(&ss, SAM3_HOST_DEFAULT, SAM3_PORT_DEFAULT, addr->base32.c_str());
|
|
if (ret < 0) {
|
|
// get error
|
|
RS_DBG("key: ", addr->base32);
|
|
RS_DBG("got error: ", ss.error);
|
|
rsAutoProxyMonitor::taskError(ticket);
|
|
} else {
|
|
addr->publicKey = ss.destkey;
|
|
rsAutoProxyMonitor::taskDone(ticket, autoProxyStatus::ok);
|
|
RS_DBG1("success");
|
|
}
|
|
});
|
|
}
|
|
|
|
void p3I2pSam3::establishConnection(taskTicket *ticket)
|
|
{
|
|
if (mState != samStatus::samState::online || !mSetting.session) {
|
|
RS_WARN("no session found!");
|
|
rsAutoProxyMonitor::taskError(ticket);
|
|
return;
|
|
}
|
|
|
|
samEstablishConnectionWrapper *wrapper = static_cast<samEstablishConnectionWrapper*>(ticket->data);
|
|
if (wrapper->address.publicKey.empty()) {
|
|
RS_ERR("no public key given");
|
|
rsAutoProxyMonitor::taskError(ticket);
|
|
return;
|
|
}
|
|
|
|
RsThread::async([ticket, this]() {
|
|
auto wrapper = static_cast<samEstablishConnectionWrapper*>(ticket->data);
|
|
|
|
struct Sam3Connection *connection;
|
|
{
|
|
RS_STACK_MUTEX(mLockSam3Access);
|
|
mSetting.session->silent = false;
|
|
connection = sam3StreamConnect(this->mSetting.session, wrapper->address.publicKey.c_str());
|
|
}
|
|
|
|
if (!connection) {
|
|
// get error
|
|
RS_DBG("got error:", this->mSetting.session->error);
|
|
rsAutoProxyMonitor::taskError(ticket);
|
|
} else {
|
|
wrapper->connection = connection;
|
|
{
|
|
RS_STACK_MUTEX(mLockSam3Access);
|
|
this->mValidConnections.push_back(connection);
|
|
}
|
|
RS_DBG1("success");
|
|
rsAutoProxyMonitor::taskDone(ticket, autoProxyStatus::ok);
|
|
}
|
|
});
|
|
}
|
|
|
|
void p3I2pSam3::closeConnection(taskTicket *ticket)
|
|
{
|
|
Sam3Connection *conn = static_cast<Sam3Connection*>(ticket->data);
|
|
|
|
if (mState == samStatus::samState::offline || !mSetting.session) {
|
|
// no session found, sam was likely stopped
|
|
RS_DBG2("no session found");
|
|
|
|
auto it = std::find(mInvalidConnections.begin(), mInvalidConnections.end(), conn);
|
|
if (it != mInvalidConnections.end()) {
|
|
// this is the expected case
|
|
mInvalidConnections.erase(it);
|
|
} else {
|
|
// this is unexpected but not a big deal, just warn
|
|
RS_WARN("cannot find connection in mInvalidConnections");
|
|
|
|
it = std::find(mValidConnections.begin(), mValidConnections.end(), conn);
|
|
if (it != mValidConnections.end()) {
|
|
mValidConnections.erase(it);
|
|
|
|
// now it is getting even weirder, still not a big deal, just warn
|
|
RS_WARN("found connection in mValidConnections");
|
|
}
|
|
}
|
|
|
|
// when libsam3 has already handled closing of the connection - which should be the case here - the memory has been freed already (-> pointer is invalid)
|
|
conn = nullptr;
|
|
} else {
|
|
RS_STACK_MUTEX(mLock);
|
|
|
|
bool callClose = true;
|
|
// search in current connections
|
|
auto it = std::find(mValidConnections.begin(), mValidConnections.end(), conn);
|
|
if (it != mValidConnections.end()) {
|
|
RS_DBG2("found valid connection");
|
|
mValidConnections.erase(it);
|
|
} else {
|
|
// search in old connections
|
|
it = std::find(mInvalidConnections.begin(), mInvalidConnections.end(), conn);
|
|
if (it != mInvalidConnections.end()) {
|
|
// old connection, just ignore. *should* be freed already
|
|
mInvalidConnections.erase(it);
|
|
|
|
RS_DBG2("found old (invalid) connection");
|
|
|
|
callClose = false;
|
|
conn = nullptr;
|
|
} else {
|
|
// weird
|
|
RS_WARN("could'n find connection!");
|
|
|
|
// best thing we can do here
|
|
callClose = false;
|
|
conn = nullptr;
|
|
}
|
|
}
|
|
|
|
if (callClose) {
|
|
RS_DBG2("closing connection");
|
|
|
|
RS_STACK_MUTEX(mLockSam3Access);
|
|
sam3CloseConnection(conn);
|
|
conn = nullptr; // freed by above call
|
|
}
|
|
}
|
|
|
|
if (conn) {
|
|
free(conn);
|
|
conn = nullptr;
|
|
}
|
|
|
|
ticket->data = nullptr;
|
|
rsAutoProxyMonitor::taskDone(ticket, autoProxyStatus::ok);
|
|
return;
|
|
}
|
|
|
|
void p3I2pSam3::updateSettings_locked()
|
|
{
|
|
RS_DBG4();
|
|
IndicateConfigChanged();
|
|
|
|
#if 0 // TODO recreat session when active, can we just recreat it?
|
|
if (mSs) {
|
|
stopSession();
|
|
startSession();
|
|
}
|
|
#endif
|
|
}
|