2020-06-10 22:06:09 +02:00

1128 lines
31 KiB
C++

/*******************************************************************************
* libretroshare/src/services/autoproxy: p3i2pbob.cc *
* *
* libretroshare: retroshare core library *
* *
* Copyright 2016 by Sehraf *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public License *
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
#include <sstream>
#include <unistd.h> /* for usleep() */
#include "p3i2pbob.h"
#include "pqi/p3peermgr.h"
#include "rsitems/rsconfigitems.h"
#include "util/radix32.h"
#include "util/radix64.h"
#include "util/rsdebug.h"
#include "util/rstime.h"
#include "util/rsprint.h"
#include "util/rsrandom.h"
static const std::string kConfigKeyBOBEnable = "BOB_ENABLE";
static const std::string kConfigKeyBOBKey = "BOB_KEY";
static const std::string kConfigKeyBOBAddr = "BOB_ADDR";
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 kConfigKeyOutLength = "OUT_LENGTH";
static const std::string kConfigKeyOutQuantity = "OUT_QUANTITY";
static const std::string kConfigKeyOutVariance = "OUT_VARIANCE";
/// Sleep duration for receiving loop in error/no-data case
static const useconds_t sleepTimeRecv = 250; // times 1000 = 250ms
/// Sleep duration for everything else
static const useconds_t sleepTimeWait = 50; // times 1000 = 50ms or 0.05s
static const int sleepFactorDefault = 10; // 0.5s
static const int sleepFactorFast = 1; // 0.05s
static const int sleepFactorSlow = 20; // 1s
static const rstime_t selfCheckPeroid = 30;
void doSleep(useconds_t timeToSleepMS) {
rstime::rs_usleep((useconds_t) (timeToSleepMS * 1000));
}
p3I2pBob::p3I2pBob(p3PeerMgr *peerMgr)
: RsTickingThread(), p3Config(),
mState(csIdel), mTask(ctIdle),
mStateOld(csIdel), mTaskOld(ctIdle),
mBOBState(bsCleared), mPeerMgr(peerMgr),
mConfigLoaded(false), mSocket(0),
mLastProxyCheck(time(NULL)),
mProcessing(NULL), mLock("I2P-BOB")
{
// set defaults
mSetting.initDefault();
mCommands.clear();
}
bool p3I2pBob::isEnabled()
{
RS_STACK_MUTEX(mLock);
return mSetting.enable;
}
bool p3I2pBob::initialSetup(std::string &addr, uint16_t &/*port*/)
{
RS_DBG("") << std::endl;
// update config
{
RS_STACK_MUTEX(mLock);
if (!mConfigLoaded) {
finalizeSettings_locked();
mConfigLoaded = true;
} else {
updateSettings_locked();
}
}
RS_DBG("config updated") << std::endl;
// request keys
// p3I2pBob::stateMachineBOB expects mProcessing to be set therefore
// we create this fake ticket without a callback or data
// ticket gets deleted later by this service
taskTicket *fakeTicket = rsAutoProxyMonitor::getTicket();
fakeTicket->task = autoProxyTask::receiveKey;
processTaskAsync(fakeTicket);
RS_DBG("fakeTicket requested") << std::endl;
// now start thread
start("I2P-BOB gen key");
RS_DBG("thread started") << std::endl;
int counter = 0;
// wait for keys
for(;;) {
doSleep(sleepTimeWait * sleepFactorDefault);
RS_STACK_MUTEX(mLock);
// wait for tast change
if (mTask != ctRunGetKeys)
break;
if (++counter > 30) {
RS_DBG4("timeout!") << std::endl;
return false;
}
}
RS_DBG("got keys") << std::endl;
// stop thread
fullstop();
RS_DBG("thread stopped") << std::endl;
{
RS_STACK_MUTEX(mLock);
addr = mSetting.address.base32;
}
RS_DBG4("addr '" + addr + "'") << std::endl;
return true;
}
void p3I2pBob::processTaskAsync(taskTicket *ticket)
{
switch (ticket->task) {
case autoProxyTask::start:
case autoProxyTask::stop:
case autoProxyTask::receiveKey:
case autoProxyTask::proxyStatusCheck:
{
RS_STACK_MUTEX(mLock);
mPending.push(ticket);
}
break;
default:
RS_DBG("unknown task") << std::endl;
rsAutoProxyMonitor::taskError(ticket);
break;
}
}
void p3I2pBob::processTaskSync(taskTicket *ticket)
{
bool data = !!ticket->data;
// check wether we can process the task immediately or have to queue it
switch (ticket->task) {
case autoProxyTask::status:
// check if everything needed is set
if (!data) {
RS_DBG("autoProxyTask::status data is missing") << std::endl;
rsAutoProxyMonitor::taskError(ticket);
break;
}
// get states
getStates((struct bobStates*)ticket->data);
// finish task
rsAutoProxyMonitor::taskDone(ticket, autoProxyStatus::ok);
break;
case autoProxyTask::getSettings:
// check if everything needed is set
if (!data) {
RS_DBG("autoProxyTask::getSettings data is missing") << std::endl;
rsAutoProxyMonitor::taskError(ticket);
break;
}
// get settings
getBOBSettings((struct bobSettings *)ticket->data);
// 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") << std::endl;
rsAutoProxyMonitor::taskError(ticket);
break;
}
// set settings
setBOBSettings((struct bobSettings *)ticket->data);
// finish task
rsAutoProxyMonitor::taskDone(ticket, autoProxyStatus::ok);
break;
case autoProxyTask::reloadConfig:
{
RS_STACK_MUTEX(mLock);
updateSettings_locked();
}
rsAutoProxyMonitor::taskDone(ticket, autoProxyStatus::ok);
break;
case autoProxyTask::getErrorInfo:
if (!data) {
RS_DBG("autoProxyTask::getErrorInfo data is missing") << std::endl;
rsAutoProxyMonitor::taskError(ticket);
} else {
RS_STACK_MUTEX(mLock);
*(std::string *)ticket->data = mErrorMsg;
rsAutoProxyMonitor::taskDone(ticket, autoProxyStatus::ok);
}
break;
default:
RS_DBG("unknown task") << std::endl;
rsAutoProxyMonitor::taskError(ticket);
break;
}
}
bool inline isAnswerOk(const std::string &answer) {
return (answer.compare(0, 2, "OK") == 0);
}
bool inline isTunnelActiveError(const std::string &answer) {
return answer.compare(0, 22, "ERROR tunnel is active") == 0;
}
void p3I2pBob::threadTick()
{
int sleepTime = 0;
{
RS_STACK_MUTEX(mLock);
std::stringstream ss;
ss << "data_tick mState: " << mState << " mTask: " << mTask << " mBOBState: " << mBOBState << " mPending: " << mPending.size();
RS_DBG4("" + ss.str()) << std::endl;
}
sleepTime += stateMachineController();
sleepTime += stateMachineBOB();
sleepTime >>= 1;
// sleep outisde of lock!
doSleep(sleepTime * sleepTimeWait);
}
int p3I2pBob::stateMachineBOB()
{
std::string answer;
bobStateInfo currentState;
{
RS_STACK_MUTEX(mLock);
if (mBOBState == bsCleared || !mConfigLoaded) {
// we don't have work to do - sleep longer
return sleepFactorSlow;
}
// get next command
currentState = mCommands[mBOBState];
}
// this call can take a while
// do NOT hold the lock
answer = executeCommand(currentState.command);
// can hold the lock for the rest of the function
RS_STACK_MUTEX(mLock);
// special state first
if (mBOBState == bsList) {
int counter = 0;
while (answer.find("OK Listing done") == std::string::npos) {
std::stringstream ss;
ss << "stateMachineBOB status check: read loop, counter: " << counter;
RS_DBG3("" + ss.str()) << std::endl;
answer += recv();
counter++;
}
if (answer.find(mTunnelName) == std::string::npos) {
RS_DBG("status check: tunnel down!") << std::endl;
// signal error
*((bool *)mProcessing->data) = true;
}
mBOBState = currentState.nextState;
} else if (isAnswerOk(answer)) {
// check for other special states
std::string key;
switch (mBOBState) {
case bsNewkeysN:
key = answer.substr(3, answer.length()-3);
mSetting.address.base32 = i2p::keyToBase32Addr(key);
IndicateConfigChanged();
break;
case bsGetkeys:
key = answer.substr(3, answer.length()-3);
mSetting.address.privateKey = key;
IndicateConfigChanged();
break;
default:
break;
}
// goto next command
mBOBState = currentState.nextState;
} else {
return stateMachineBOB_locked_failure(answer, currentState);
}
return sleepFactorFast;
}
int p3I2pBob::stateMachineBOB_locked_failure(const std::string &answer, const bobStateInfo &currentState)
{
// wait in case of active tunnel
// happens when trying to clear a stopping tunnel
if (isTunnelActiveError(answer)) {
return sleepFactorDefault;
}
RS_DBG("FAILED to run command '" + currentState.command + "'") << std::endl;
RS_DBG("'" + answer + "'") << std::endl;
mErrorMsg.append("FAILED to run command '" + currentState.command + "'" + '\n');
mErrorMsg.append("reason '" + answer + "'" + '\n');
// this error handling needs testing!
mStateOld = mState;
mState = csError;
switch (mBOBState) {
case bsGetnick:
// failed getting nick
// tunnel is probably non existing
case bsClear:
// tunnel is cleared
mBOBState = bsQuit;
break;
case bsStop:
// failed stopping
// tunnel us probably not running
// continue to clearing
mBOBState = bsClear;
break;
case bsQuit:
// this can happen when the
// connection is somehow broken
// just try to disconnect
disconnectI2P();
mBOBState = bsCleared;
break;
default:
// try to recover
mBOBState = bsGetnick;
break;
}
return sleepFactorFast;
}
int p3I2pBob::stateMachineController()
{
RS_STACK_MUTEX(mLock);
switch (mState) {
case csIdel:
return stateMachineController_locked_idle();
case csDoConnect:
if (!connectI2P()) {
RS_DBG("doConnect: unable to connect") << std::endl;
mStateOld = mState;
mState = csError;
mErrorMsg = "unable to connect to BOB port";
return sleepFactorSlow;
}
RS_DBG4("doConnect: connected") << std::endl;
mState = csConnected;
break;
case csConnected:
return stateMachineController_locked_connected();
case csWaitForBob:
// check connection problems
if (mSocket == 0) {
RS_DBG("waitForBob: conection lost") << std::endl;
mStateOld = mState;
mState = csError;
mErrorMsg = "connection lost to BOB";
return sleepFactorDefault;
}
// check for finished BOB protocol
if (mBOBState == bsCleared) {
// done
RS_DBG4("waitForBob: mBOBState == bsCleared") << std::endl;
mState = csDoDisconnect;
}
break;
case csDoDisconnect:
if (!disconnectI2P() || mSocket != 0) {
// just in case
RS_DBG("doDisconnect: can't disconnect") << std::endl;
mStateOld = mState;
mState = csError;
mErrorMsg = "unable to disconnect from BOB";
return sleepFactorDefault;
}
RS_DBG4("doDisconnect: disconnected") << std::endl;
mState = csDisconnected;
break;
case csDisconnected:
return stateMachineController_locked_disconnected();
case csError:
return stateMachineController_locked_error();
}
return sleepFactorFast;
}
int p3I2pBob::stateMachineController_locked_idle()
{
// do some sanity checks
// use asserts becasue these things indicate wrong/broken state machines that need to be fixed ASAP!
assert(mBOBState == bsCleared);
assert(mSocket == 0);
assert(mState == csIdel || mState == csDisconnected);
controllerTask oldTask = mTask;
// check for new task
if (mProcessing == NULL && !mPending.empty()) {
mProcessing = mPending.front();
mPending.pop();
if (!mSetting.enable && (
mProcessing->task == autoProxyTask::start ||
mProcessing->task == autoProxyTask::stop ||
mProcessing->task == autoProxyTask::proxyStatusCheck)) {
// skip since we are not enabled
RS_DBG1("disabled -> skipping ticket") << std::endl;
rsAutoProxyMonitor::taskDone(mProcessing, autoProxyStatus::disabled);
mProcessing = NULL;
} else {
// set states
switch (mProcessing->task) {
case autoProxyTask::start:
mLastProxyCheck = time(NULL);
mTask = ctRunSetUp;
break;
case autoProxyTask::stop:
mTask = ctRunShutDown;
break;
case autoProxyTask::receiveKey:
mTaskOld = mTask;
mTask = ctRunGetKeys;
break;
case autoProxyTask::proxyStatusCheck:
mTaskOld = mTask;
mTask = ctRunCheck;
break;
default:
RS_DBG1("unknown async task") << std::endl;
rsAutoProxyMonitor::taskError(mProcessing);
mProcessing = NULL;
break;
}
}
mErrorMsg.clear();
}
// periodically check
if (mTask == ctRunSetUp && mLastProxyCheck < time(NULL) - selfCheckPeroid) {
taskTicket *tt = rsAutoProxyMonitor::getTicket();
tt->task = autoProxyTask::proxyStatusCheck;
tt->data = (void *) new bool;
*((bool *)tt->data) = false;
mPending.push(tt);
mLastProxyCheck = time(NULL);
}
// wait for new task
if (!!mProcessing) {
// check if task was changed
if (mTask != oldTask) {
mState = csDoConnect;
} else {
// A ticket shall be processed but the state didn't change.
// This means that what ever is requested in the ticket
// was requested before already.
// -> set mState to csDisconnected to answer the ticket
mState = csDisconnected;
}
return sleepFactorFast;
}
return sleepFactorSlow;
}
int p3I2pBob::stateMachineController_locked_connected()
{
// set proper bob state
switch (mTask) {
case ctRunSetUp:
// when we have a key use it for server tunnel!
if(mSetting.address.privateKey.empty()) {
RS_DBG4("setting mBOBState = setnickC") << std::endl;
mBOBState = bsSetnickC;
} else {
RS_DBG4("setting mBOBState = setnickS") << std::endl;
mBOBState = bsSetnickS;
}
break;
case ctRunShutDown:
// shut down existing tunnel
RS_DBG4("setting mBOBState = getnick") << std::endl;
mBOBState = bsGetnick;
break;
case ctRunCheck:
RS_DBG4("setting mBOBState = list") << std::endl;
mBOBState = bsList;
break;
case ctRunGetKeys:
RS_DBG4("setting mBOBState = setnickN") << std::endl;
mBOBState = bsSetnickN;
break;
case ctIdle:
RS_DBG("task is idle. This should not happen!") << std::endl;
break;
}
mState = csWaitForBob;
return sleepFactorFast;
}
int p3I2pBob::stateMachineController_locked_disconnected()
{
// check if we had an error
bool errorHappened = (mStateOld == csError);
if(errorHappened) {
// reset old state
mStateOld = csIdel;
RS_DBG("error during process!") << std::endl;
}
// answer ticket
controllerState newState = csIdel;
switch (mTask) {
case ctRunSetUp:
if (errorHappened) {
rsAutoProxyMonitor::taskError(mProcessing);
// switch to error
newState = csError;
} else {
rsAutoProxyMonitor::taskDone(mProcessing, autoProxyStatus::online);
}
break;
case ctRunShutDown:
// don't care about error here
rsAutoProxyMonitor::taskDone(mProcessing, autoProxyStatus::offline);
break;
case ctRunCheck:
// get result and delete dummy ticket
errorHappened |= *((bool *)mProcessing->data);
delete (bool *)mProcessing->data;
delete mProcessing;
// restore old task
mTask = mTaskOld;
if (!errorHappened) {
RS_DBG4("run check result: ok") << std::endl;
break;
}
// switch to error
newState = csError;
RS_DBG("run check result: error") << std::endl;
mErrorMsg = "Connection check failed. Will try to restart tunnel.";
break;
case ctRunGetKeys:
if (!errorHappened) {
// rebuild commands
updateSettings_locked();
if (mProcessing->data)
*((struct bobSettings *)mProcessing->data) = mSetting;
rsAutoProxyMonitor::taskDone(mProcessing, autoProxyStatus::ok);
} else {
rsAutoProxyMonitor::taskError(mProcessing);
// switch to error
newState = csError;
}
// restore old task
mTask = mTaskOld;
break;
case ctIdle:
RS_DBG("task is idle. This should not happen!") << std::endl;
rsAutoProxyMonitor::taskError(mProcessing);
}
mProcessing = NULL;
mState = newState;
if (newState == csError)
mLastProxyCheck = time(NULL);
return sleepFactorFast;
}
int p3I2pBob::stateMachineController_locked_error()
{
// wait for bob protocoll
if (mBOBState != bsCleared) {
RS_DBG4("waiting for BOB") << std::endl;
return sleepFactorFast;
}
#if 0
std::stringstream ss;
ss << "stateMachineController_locked_error: mProcessing: " << (mProcessing ? "not null" : "null");
RS_DBG4("" + ss.str()) << std::endl;
#endif
// try to finish ticket
if (mProcessing) {
switch (mTask) {
case ctRunCheck:
// connection check failed at some point
RS_DBG("failed to check proxy status (it's likely dead)!") << std::endl;
*((bool *)mProcessing->data) = true;
mState = csDoDisconnect;
mStateOld = csIdel;
// keep the error message
break;
case ctRunShutDown:
// not a big deal though
RS_DBG("failed to shut down tunnel (it's likely dead though)!") << std::endl;
mState = csDoDisconnect;
mStateOld = csIdel;
mErrorMsg.clear();
break;
case ctIdle:
// should not happen but we need to deal with it
// this will produce some error messages in the log and finish the task (marked as failed)
RS_DBG("task is idle. This should not happen!") << std::endl;
mState = csDoDisconnect;
mStateOld = csIdel;
mErrorMsg.clear();
break;
case ctRunGetKeys:
case ctRunSetUp:
RS_DBG("failed to receive key / start up") << std::endl;
mStateOld = csError;
mState = csDoDisconnect;
// keep the error message
break;
}
return sleepFactorFast;
}
// periodically retry
if (mLastProxyCheck < time(NULL) - (selfCheckPeroid >> 1) && mTask == ctRunSetUp) {
RS_DBG("retrying") << std::endl;
mLastProxyCheck = time(NULL);
mErrorMsg.clear();
// create fake ticket
taskTicket *tt = rsAutoProxyMonitor::getTicket();
tt->task = autoProxyTask::start;
mPending.push(tt);
}
// check for new tickets
if (!mPending.empty()) {
RS_DBG4("processing new ticket") << std::endl;
// reset and try new task
mTask = ctIdle;
mState = csIdel;
return sleepFactorFast;
}
return sleepFactorDefault;
}
RsSerialiser *p3I2pBob::setupSerialiser()
{
RsSerialiser* rsSerialiser = new RsSerialiser();
rsSerialiser->addSerialType(new RsGeneralConfigSerialiser());
return rsSerialiser;
}
#define addKVS(_vitem, _kv, _key, _value) \
_kv.key = _key;\
_kv.value = _value;\
_vitem->tlvkvs.pairs.push_back(_kv);
#define addKVSInt(_vitem, _kv, _key, _value) \
_kv.key = _key;\
rs_sprintf(_kv.value, "%d", _value);\
_vitem->tlvkvs.pairs.push_back(_kv);
bool p3I2pBob::saveList(bool &cleanup, std::list<RsItem *> &lst)
{
RS_DBG4("") << std::endl;
cleanup = true;
RsConfigKeyValueSet *vitem = new RsConfigKeyValueSet;
RsTlvKeyValue kv;
RS_STACK_MUTEX(mLock);
addKVS(vitem, kv, kConfigKeyBOBEnable, mSetting.enable ? "TRUE" : "FALSE")
addKVS(vitem, kv, kConfigKeyBOBKey, mSetting.address.privateKey)
addKVS(vitem, kv, kConfigKeyBOBAddr, mSetting.address.base32)
addKVSInt(vitem, kv, kConfigKeyInLength, mSetting.inLength)
addKVSInt(vitem, kv, kConfigKeyInQuantity, mSetting.inQuantity)
addKVSInt(vitem, kv, kConfigKeyInVariance, mSetting.inVariance)
addKVSInt(vitem, kv, kConfigKeyOutLength, mSetting.outLength)
addKVSInt(vitem, kv, kConfigKeyOutQuantity, mSetting.outQuantity)
addKVSInt(vitem, kv, kConfigKeyOutVariance, mSetting.outVariance)
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 p3I2pBob::loadList(std::list<RsItem *> &load)
{
RS_DBG4("") << std::endl;
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 == kConfigKeyBOBEnable)
mSetting.enable = kit->value == "TRUE";
else if (kit->key == kConfigKeyBOBKey)
mSetting.address.privateKey = kit->value;
else if (kit->key == kConfigKeyBOBAddr)
mSetting.address.base32 = kit->value;
getKVSUInt(kit, kConfigKeyInLength, mSetting.inLength)
getKVSUInt(kit, kConfigKeyInQuantity, mSetting.inQuantity)
getKVSUInt(kit, kConfigKeyInVariance, mSetting.inVariance)
getKVSUInt(kit, kConfigKeyOutLength, mSetting.outLength)
getKVSUInt(kit, kConfigKeyOutQuantity, mSetting.outQuantity)
getKVSUInt(kit, kConfigKeyOutVariance, mSetting.outVariance)
else
RS_DBG("unknown key: " + kit->key) << std::endl;
}
}
delete vitem;
}
RS_STACK_MUTEX(mLock);
finalizeSettings_locked();
mConfigLoaded = true;
return true;
}
#undef getKVSUInt
void p3I2pBob::getBOBSettings(bobSettings *settings)
{
if (settings == NULL)
return;
RS_STACK_MUTEX(mLock);
*settings = mSetting;
}
void p3I2pBob::setBOBSettings(const bobSettings *settings)
{
if (settings == NULL)
return;
RS_STACK_MUTEX(mLock);
mSetting = *settings;
IndicateConfigChanged();
// Note:
// We don't take care of updating a running BOB session here
// This can be done manually by stoping and restarting the session
// Note2:
// In case there is no config yet to load
// finalize settings here instead
if (!mConfigLoaded) {
finalizeSettings_locked();
mConfigLoaded = true;
} else {
updateSettings_locked();
}
}
void p3I2pBob::getStates(bobStates *bs)
{
if (bs == NULL)
return;
RS_STACK_MUTEX(mLock);
bs->cs = mState;
bs->ct = mTask;
bs->bs = mBOBState;
bs->tunnelName = mTunnelName;
}
std::string p3I2pBob::executeCommand(const std::string &command)
{
RS_DBG4("running '" + command + "'") << std::endl;
std::string copy = command;
copy.push_back('\n');
// send command
// there is only one thread that touches mSocket - no need for a lock
::send(mSocket, copy.c_str(), copy.size(), 0);
// receive answer (trailing new line is already removed!)
std::string ans = recv();
RS_DBG4("answer '" + ans + "'") << std::endl;
return ans;
}
bool p3I2pBob::connectI2P()
{
// there is only one thread that touches mSocket - no need for a lock
if (mSocket != 0) {
RS_DBG("mSocket != 0") << std::endl;
return false;
}
// create socket
mSocket = unix_socket(PF_INET, SOCK_STREAM, 0);
if (mSocket < 0)
{
RS_DBG("Failed to open socket! Socket Error: " + socket_errorType(errno)) << std::endl;
return false;
}
// connect
int err = unix_connect(mSocket, mI2PProxyAddr);
if (err != 0) {
RS_DBG("Failed to connect to BOB! Socket Error: " + socket_errorType(errno)) << std::endl;
return false;
}
// receive hello msg
recv();
RS_DBG4("done") << std::endl;
return true;
}
bool p3I2pBob::disconnectI2P()
{
// there is only one thread that touches mSocket - no need for a lock
if (mSocket == 0) {
RS_DBG("mSocket == 0") << std::endl;
return true;
}
int err = unix_close(mSocket);
if (err != 0) {
RS_DBG("Failed to close socket! Socket Error: " + socket_errorType(errno)) << std::endl;
return false;
}
RS_DBG4("done") << std::endl;
mSocket = 0;
return true;
}
std::string toString(const std::string &a, const int b) {
std::ostringstream oss;
oss << b;
return a + oss.str();;
}
std::string toString(const std::string &a, const uint16_t b) {
return toString(a, (int)b);
}
std::string toString(const std::string &a, const int8_t b) {
return toString(a, (int)b);
}
void p3I2pBob::finalizeSettings_locked()
{
RS_DBG4("") << std::endl;
sockaddr_storage_clear(mI2PProxyAddr);
// get i2p proxy addr
sockaddr_storage proxy;
mPeerMgr->getProxyServerAddress(RS_HIDDEN_TYPE_I2P, proxy);
// overwrite port to bob port
sockaddr_storage_setipv4(mI2PProxyAddr, (sockaddr_in*)&proxy);
sockaddr_storage_setport(mI2PProxyAddr, 2827);
RS_DBG4("using " + sockaddr_storage_tostring(mI2PProxyAddr)) << std::endl;
RS_DBG4("using " + mSetting.address.base32) << std::endl;
peerState ps;
mPeerMgr->getOwnNetStatus(ps);
// setup commands
// new lines are appended later!
// generate random suffix for name
// RSRandom::random_alphaNumericString can return very weird looking strings like: ,,@z+M
// use base32 instead
size_t len = 5; // 5 characters = 8 base32 symbols
std::vector<uint8_t> tmp(len);
RSRandom::random_bytes(tmp.data(), len);
const std::string location = Radix32::encode(tmp.data(), len);
RS_DBG4("using suffix " + location) << std::endl;
mTunnelName = "RetroShare-" + location;
const std::string setnick = "setnick RetroShare-" + location;
const std::string getnick = "getnick RetroShare-" + location;
const std::string newkeys = "newkeys";
const std::string getkeys = "getkeys";
const std::string setkeys = "setkeys " + mSetting.address.privateKey;
const std::string inhost = "inhost " + sockaddr_storage_iptostring(proxy);
const std::string inport = toString("inport ", sockaddr_storage_port(proxy));
const std::string outhost = "outhost " + sockaddr_storage_iptostring(ps.localaddr);
const std::string outport = toString("outport ", sockaddr_storage_port(ps.localaddr));
// length
const std::string inlength = toString("option inbound.length=", mSetting.inLength);
const std::string outlength = toString("option outbound.length=", mSetting.outLength);
// variance
const std::string invariance = toString("option inbound.lengthVariance=", mSetting.inVariance);
const std::string outvariance= toString("option outbound.lengthVariance=", mSetting.outVariance);
// quantity
const std::string inquantity = toString("option inbound.quantity=", mSetting.inQuantity);
const std::string outquantity= toString("option outbound.quantity=", mSetting.outQuantity);
const std::string quiet = "quiet true";
const std::string start = "start";
const std::string stop = "stop";
const std::string clear = "clear";
const std::string list = "list";
const std::string quit = "quit";
// setup state machine
// start chain
// -> A: server and client tunnel
mCommands[bsSetnickS] = {setnick, bsSetkeys};
mCommands[bsSetkeys] = {setkeys, bsOuthost};
mCommands[bsOuthost] = {outhost, bsOutport};
mCommands[bsOutport] = {outport, bsInhost};
// -> B: only client tunnel
mCommands[bsSetnickC] = {setnick, bsNewkeysC};
mCommands[bsNewkeysC] = {newkeys, bsInhost};
// -> both
mCommands[bsInhost] = {inhost, bsInport};
mCommands[bsInport] = {inport, bsInlength};
mCommands[bsInlength] = {inlength, bsOutlength};
mCommands[bsOutlength] = {outlength, bsInvariance};
mCommands[bsInvariance] = {invariance, bsOutvariance};
mCommands[bsOutvariance]= {outvariance,bsInquantity};
mCommands[bsInquantity] = {inquantity, bsOutquantity};
mCommands[bsOutquantity]= {outquantity,bsQuiet};
mCommands[bsQuiet] = {quiet, bsStart};
mCommands[bsStart] = {start, bsQuit};
mCommands[bsQuit] = {quit, bsCleared};
// stop chain
mCommands[bsGetnick] = {getnick, bsStop};
mCommands[bsStop] = {stop, bsClear};
mCommands[bsClear] = {clear, bsQuit};
// getkeys chain
mCommands[bsSetnickN] = {setnick, bsNewkeysN};
mCommands[bsNewkeysN] = {newkeys, bsGetkeys};
mCommands[bsGetkeys] = {getkeys, bsClear};
// list chain
mCommands[bsList] = {list, bsQuit};
}
void p3I2pBob::updateSettings_locked()
{
RS_DBG4("") << std::endl;
sockaddr_storage proxy;
mPeerMgr->getProxyServerAddress(RS_HIDDEN_TYPE_I2P, proxy);
peerState ps;
mPeerMgr->getOwnNetStatus(ps);
const std::string setkeys = "setkeys " + mSetting.address.privateKey;
const std::string inhost = "inhost " + sockaddr_storage_iptostring(proxy);
const std::string inport = toString("inport ", sockaddr_storage_port(proxy));
const std::string outhost = "outhost " + sockaddr_storage_iptostring(ps.localaddr);
const std::string outport = toString("outport ", sockaddr_storage_port(ps.localaddr));
// length
const std::string inlength = toString("option inbound.length=", mSetting.inLength);
const std::string outlength = toString("option outbound.length=", mSetting.outLength);
// variance
const std::string invariance = toString("option inbound.lengthVariance=", mSetting.inVariance);
const std::string outvariance= toString("option outbound.lengthVariance=", mSetting.outVariance);
// quantity
const std::string inquantity = toString("option inbound.quantity=", mSetting.inQuantity);
const std::string outquantity= toString("option outbound.quantity=", mSetting.outQuantity);
mCommands[bsSetkeys] = {setkeys, bsOuthost};
mCommands[bsOuthost] = {outhost, bsOutport};
mCommands[bsOutport] = {outport, bsInhost};
mCommands[bsInhost] = {inhost, bsInport};
mCommands[bsInport] = {inport, bsInlength};
mCommands[bsInlength] = {inlength, bsOutlength};
mCommands[bsOutlength] = {outlength, bsInvariance};
mCommands[bsInvariance] = {invariance, bsOutvariance};
mCommands[bsOutvariance]= {outvariance,bsInquantity};
mCommands[bsInquantity] = {inquantity, bsOutquantity};
mCommands[bsOutquantity]= {outquantity,bsQuiet};
}
std::string p3I2pBob::recv()
{
// BOB works line based
// -> \n indicates and of the line
constexpr uint16_t bufferSize = 128;
char buffer[bufferSize];
std::string ans;
uint16_t retry = 10;
do {
memset(buffer, 0, bufferSize);
// peek at data
auto length = ::recv(mSocket, buffer, bufferSize, MSG_PEEK);
if (length <= 0) {
if (length < 0) {
// error
perror(__PRETTY_FUNCTION__);
}
retry--;
doSleep(sleepTimeRecv);
continue;
}
// at least one byte was read
// search for new line
auto bufferStr = std::string(buffer);
size_t pos = bufferStr.find('\n');
if (pos == std::string::npos) {
// no new line found -> more to read
// sanity check
if (length != bufferSize) {
// expectation: a full buffer was peeked)
RS_DBG1("peeked less than bufferSize but also didn't found a new line character") << std::endl;
}
// this should never happen
assert(length <= bufferSize);
} else {
// new line found -> end of message
// calculate how much there is to read, read the \n, too!
length = pos + 1;
// end loop
retry = 0;
}
// now read for real
memset(buffer, 0, bufferSize);
length = ::recv(mSocket, buffer, length, 0);
bufferStr = std::string(buffer);
ans.append(bufferStr);
} while(retry > 0);
return ans;
}