From 10189ba4d0f5a9e16e0d705097435675c476bec3 Mon Sep 17 00:00:00 2001 From: sehraf Date: Sun, 25 Oct 2020 12:50:55 +0100 Subject: [PATCH] add i2psam3 --- libretroshare/src/libretroshare.pro | 27 +- libretroshare/src/pqi/pqissl.cc | 1 + libretroshare/src/pqi/pqissli2psam3.cpp | 304 ++++++ libretroshare/src/pqi/pqissli2psam3.h | 53 ++ libretroshare/src/pqi/pqissllistener.h | 4 +- libretroshare/src/pqi/pqisslpersongrp.cc | 20 +- libretroshare/src/retroshare/rsinit.h | 2 +- libretroshare/src/rsserver/p3face.h | 6 +- libretroshare/src/rsserver/rsinit.cc | 51 +- .../src/services/autoproxy/p3i2psam3.cpp | 894 ++++++++++++++++++ .../src/services/autoproxy/p3i2psam3.h | 128 +++ .../services/autoproxy/rsautoproxymonitor.cc | 20 +- .../services/autoproxy/rsautoproxymonitor.h | 24 +- libretroshare/src/use_libretroshare.pri | 9 + libretroshare/src/util/i2pcommon.cpp | 7 +- libretroshare/src/util/i2pcommon.h | 2 +- plugins/plugins.pro | 2 +- retroshare-gui/src/gui/GenCertDialog.cpp | 6 +- retroshare-gui/src/gui/settings/ServerPage.ui | 29 +- retroshare.pri | 21 +- supportlibs/libsam3/src/libsam3/libsam3.c | 18 +- 21 files changed, 1543 insertions(+), 85 deletions(-) create mode 100644 libretroshare/src/pqi/pqissli2psam3.cpp create mode 100644 libretroshare/src/pqi/pqissli2psam3.h create mode 100644 libretroshare/src/services/autoproxy/p3i2psam3.cpp create mode 100644 libretroshare/src/services/autoproxy/p3i2psam3.h diff --git a/libretroshare/src/libretroshare.pro b/libretroshare/src/libretroshare.pro index 84d18944e..c8f9c4b95 100644 --- a/libretroshare/src/libretroshare.pro +++ b/libretroshare/src/libretroshare.pro @@ -157,7 +157,7 @@ rs_webui { HEADERS += plugins/pluginmanager.h \ plugins/dlfcn_win32.h \ - rsitems/rspluginitems.h \ + rsitems/rspluginitems.h \ util/i2pcommon.h \ util/rsinitedptr.h @@ -1014,6 +1014,31 @@ rs_broadcast_discovery { } } +rs_sam3 { + SOURCES += \ + services/autoproxy/p3i2psam3.cpp \ + pqi/pqissli2psam3.cpp \ + + HEADERS += \ + services/autoproxy/p3i2psam3.h \ + pqi/pqissli2psam3.h \ +} + +rs_sam3_libsam3 { + DUMMYQMAKECOMPILERINPUT = FORCE + libsam3.name = Generating libsam3. + libsam3.input = DUMMYQMAKECOMPILERINPUT + libsam3.output = $$clean_path($${LIBSAM3_BUILD_PATH}/libsam3.a) + libsam3.CONFIG += target_predeps combine + libsam3.variable_out = PRE_TARGETDEPS + libsam3.commands = \ + cd $${RS_SRC_PATH} && \ + cp -r $${LIBSAM3_SRC_PATH}/* $${LIBSAM3_BUILD_PATH} && \ + cd $${LIBSAM3_BUILD_PATH} && \ + $(MAKE) build + QMAKE_EXTRA_COMPILERS += libsam3 +} + ########################################################################################################### # OLD CONFIG OPTIONS. # Not used much - but might be useful one day. diff --git a/libretroshare/src/pqi/pqissl.cc b/libretroshare/src/pqi/pqissl.cc index 76d447cc8..8ba82a105 100644 --- a/libretroshare/src/pqi/pqissl.cc +++ b/libretroshare/src/pqi/pqissl.cc @@ -1742,6 +1742,7 @@ bool pqissl::moretoread(uint32_t usec) { rslog(RSL_ALERT, pqisslzone, "pqissl::moretoread() Select ERROR!"); + RS_WARN(errno); return 0; } diff --git a/libretroshare/src/pqi/pqissli2psam3.cpp b/libretroshare/src/pqi/pqissli2psam3.cpp new file mode 100644 index 000000000..ec6497e36 --- /dev/null +++ b/libretroshare/src/pqi/pqissli2psam3.cpp @@ -0,0 +1,304 @@ +#include "pqissli2psam3.h" + +#ifdef RS_USE_I2P_SAM3_I2PSAM +#include "util/i2psam.h" +#endif +#ifdef RS_USE_I2P_SAM3_LIBSAM3 +#include +#endif + +RS_SET_CONTEXT_DEBUG_LEVEL(2) + +static constexpr int pqiDone = 1; +static constexpr int pqiWait = 0; +static constexpr int pqiError = -1; + +pqissli2psam3::pqissli2psam3(pqissllistener *l, PQInterface *parent, p3LinkMgr *lm) + : pqissl(l, parent, lm), mState(pqisslSam3State::NONE), mI2pAddrB32(), mI2pAddrLong() +{ + RS_DBG4(); + mConn = nullptr; +} + +bool pqissli2psam3::connect_parameter(uint32_t type, const std::string &value) +{ + RS_DBG4(); + + if (type == NET_PARAM_CONNECT_DOMAIN_ADDRESS) + { + RS_DBG1("got addr:", value); + RS_STACK_MUTEX(mSslMtx); + mI2pAddrB32 = value; + return true; + } + + return pqissl::connect_parameter(type, value); +} + +int pqissli2psam3::Initiate_Connection() +{ + RS_DBG4(); + + if(waiting != WAITING_DELAY) + { + RS_ERR("Already Attempt in Progress!"); + return pqiError; + } + + switch (mState) { + case(pqisslSam3State::NONE): + RS_DBG2("NONE"); + { + if(mConn) { + // how did we end up here? +#ifdef RS_USE_I2P_SAM3_I2PSAM + unix_close(mConn); +#endif +#ifdef RS_USE_I2P_SAM3_LIBSAM3 + sam3CloseConnection(mConn); +#endif + } + mConn = 0; + // get SAM session + mConn = 0; + samSettings ss; + ss.session = nullptr; + rsAutoProxyMonitor::taskSync(autoProxyType::I2PSAM3, autoProxyTask::getSettings, static_cast(&ss)); + +#ifdef RS_USE_I2P_SAM3_I2PSAM + if (!!ss.session && !ss.session->isSick()) { +#endif +#ifdef RS_USE_I2P_SAM3_LIBSAM3 + if (!!ss.session) { +#endif + RS_DBG3("NONE->DO_LOOKUP"); + mState = pqisslSam3State::DO_LOOKUP; + } else { + RS_DBG3("NONE->DO_LOOKUP NOPE", ss.session); + } + } + break; + case(pqisslSam3State::DO_LOOKUP): + RS_DBG1("DO_LOOKUP"); + + if (!mI2pAddrLong.empty()) { + // skip lookup, it is highly unlikely/impossible for a public key to change (isn't it?) + mState = pqisslSam3State::WAIT_LOOKUP; + break; + } + + { + i2p::address *addr = new i2p::address; + addr->clear(); + addr->base32 = mI2pAddrB32; + rsAutoProxyMonitor::taskAsync(autoProxyType::I2PSAM3, autoProxyTask::lookupKey, this, static_cast(addr)); + } + mState = pqisslSam3State::WAIT_LOOKUP; + break; + case(pqisslSam3State::DO_CONNECT): + RS_DBG2("DO_CONNECT"); + + { + auto wrapper = new samEstablishConnectionWrapper(); + wrapper->address.clear(); + wrapper->address.publicKey = mI2pAddrLong; +#ifdef RS_USE_I2P_SAM3_I2PSAM + wrapper->socket = 0; +#endif +#ifdef RS_USE_I2P_SAM3_LIBSAM3 + wrapper->connection = nullptr; +#endif + rsAutoProxyMonitor::taskAsync(autoProxyType::I2PSAM3, autoProxyTask::establishConnection, this, static_cast(wrapper)); + } + mState = pqisslSam3State::WAIT_CONNECT; + break; + case(pqisslSam3State::DONE): + RS_DBG2("DONE"); + + if (setupSocket()) + return pqiDone; + return pqiError; + + /* waiting */ + case(pqisslSam3State::WAIT_LOOKUP): + RS_DBG3("WAIT_LOOKUP"); + break; + case(pqisslSam3State::WAIT_CONNECT): + RS_DBG3("WAIT_CONNECT"); + break; + } + return pqiWait; +} + +int pqissli2psam3::net_internal_close(int fd) +{ + RS_DBG4(); + + // sanity check +#ifdef RS_USE_I2P_SAM3_I2PSAM + if (mConn && fd != mConn) { +#endif +#ifdef RS_USE_I2P_SAM3_LIBSAM3 + if (mConn && fd != mConn->fd) { +#endif + // this should never happen! +//#ifdef RS_USE_I2P_SAM3_I2PSAM +// unix_close(mConn); +//#endif +//#ifdef RS_USE_I2P_SAM3_LIBSAM3 + RS_ERR("fd != mConn"); +// sam3CloseConnection(mConn); +//#endif + } + + // now to the actuall closing + int ret = pqissl::net_internal_close(fd); +// int ret = 0; +//#ifdef RS_USE_I2P_SAM3_LIBSAM3 +// if (mConn) +// ret = sam3CloseConnection(mConn); +//#endif + rsAutoProxyMonitor::taskAsync(autoProxyType::I2PSAM3, autoProxyTask::closeConnection, this, mConn), + + // finally cleanup + mConn = 0; + mState = pqisslSam3State::NONE; + + return ret; +} + +void pqissli2psam3::taskFinished(taskTicket *&ticket) +{ + RS_DBG4(); + + switch (ticket->task) { + case autoProxyTask::lookupKey: + { + auto addr = static_cast(ticket->data); + + RS_STACK_MUTEX(mSslMtx); + if (ticket->result == autoProxyStatus::ok) { + mI2pAddrLong = addr->publicKey; + mState = pqisslSam3State::DO_CONNECT; + } else { + waiting = WAITING_FAIL_INTERFACE; + } + + delete addr; + ticket->data = nullptr; + addr = nullptr; + } + break; + case autoProxyTask::establishConnection: + { + auto wrapper = static_cast(ticket->data); + + RS_STACK_MUTEX(mSslMtx); + if (ticket->result == autoProxyStatus::ok) { +#ifdef RS_USE_I2P_SAM3_I2PSAM + mConn = wrapper->socket; +#endif +#ifdef RS_USE_I2P_SAM3_LIBSAM3 + mConn = wrapper->connection; +#endif + mState = pqisslSam3State::DONE; + } else { + waiting = WAITING_FAIL_INTERFACE; + } + + delete wrapper; + ticket->data = nullptr; + wrapper = nullptr; + } + break; + case autoProxyTask::closeConnection: + // nothing to do here + break; + default: + RS_WARN("unkown task", ticket->task); + } + + // clean up! + delete ticket; + ticket = nullptr; +} + +bool pqissli2psam3::setupSocket() +{ + /* + * This function contains the generis part from pqissl::Initiate_Connection() + */ + int err; +#ifdef RS_USE_I2P_SAM3_I2PSAM + int osock = mConn; +#endif +#ifdef RS_USE_I2P_SAM3_LIBSAM3 + int osock = mConn->fd; +#endif + + err = unix_fcntl_nonblock(osock); + if (err < 0) + { + RS_ERR("Cannot make socket NON-Blocking:", err); + + waiting = WAITING_FAIL_INTERFACE; + net_internal_close(osock); + return false; + } + +#ifdef WINDOWS_SYS + /* Set TCP buffer size for Windows systems */ + + int sockbufsize = 0; + int size = sizeof(int); + + err = getsockopt(osock, SOL_SOCKET, SO_RCVBUF, (char *)&sockbufsize, &size); +#ifdef PQISSL_DEBUG + if (err == 0) { + std::cerr << "pqissl::Initiate_Connection: Current TCP receive buffer size " << sockbufsize << std::endl; + } else { + std::cerr << "pqissl::Initiate_Connection: Error getting TCP receive buffer size. Error " << err << std::endl; + } +#endif + + sockbufsize = 0; + + err = getsockopt(osock, SOL_SOCKET, SO_SNDBUF, (char *)&sockbufsize, &size); +#ifdef PQISSL_DEBUG + if (err == 0) { + std::cerr << "pqissl::Initiate_Connection: Current TCP send buffer size " << sockbufsize << std::endl; + } else { + std::cerr << "pqissl::Initiate_Connection: Error getting TCP send buffer size. Error " << err << std::endl; + } +#endif + + sockbufsize = WINDOWS_TCP_BUFFER_SIZE; + + err = setsockopt(osock, SOL_SOCKET, SO_RCVBUF, (char *)&sockbufsize, sizeof(sockbufsize)); +#ifdef PQISSL_DEBUG + if (err == 0) { + std::cerr << "pqissl::Initiate_Connection: TCP receive buffer size set to " << sockbufsize << std::endl; + } else { + std::cerr << "pqissl::Initiate_Connection: Error setting TCP receive buffer size. Error " << err << std::endl; + } +#endif + + err = setsockopt(osock, SOL_SOCKET, SO_SNDBUF, (char *)&sockbufsize, sizeof(sockbufsize)); +#ifdef PQISSL_DEBUG + if (err == 0) { + std::cerr << "pqissl::Initiate_Connection: TCP send buffer size set to " << sockbufsize << std::endl; + } else { + std::cerr << "pqissl::Initiate_Connection: Error setting TCP send buffer size. Error " << err << std::endl; + } +#endif +#endif // WINDOWS_SYS + + + mTimeoutTS = time(NULL) + mConnectTimeout; + //std::cerr << "Setting Connect Timeout " << mConnectTimeout << " Seconds into Future " << std::endl; + + waiting = WAITING_SOCK_CONNECT; + sockfd = osock; + + return true; +} diff --git a/libretroshare/src/pqi/pqissli2psam3.h b/libretroshare/src/pqi/pqissli2psam3.h new file mode 100644 index 000000000..83cba419f --- /dev/null +++ b/libretroshare/src/pqi/pqissli2psam3.h @@ -0,0 +1,53 @@ +#ifndef PQISSLI2PSAM3_H +#define PQISSLI2PSAM3_H + +#include "pqi/pqissl.h" +#include "services/autoproxy/rsautoproxymonitor.h" +#include "services/autoproxy/p3i2psam3.h" + +// Use a state machine as the whole pqi code is designed around them and some operation (like lookup) might be blocking +enum class pqisslSam3State : uint8_t { + NONE = 0, + DO_LOOKUP, + WAIT_LOOKUP, + DO_CONNECT, + WAIT_CONNECT, + DONE +}; + +class pqissli2psam3 : public pqissl, public autoProxyCallback +{ +public: + pqissli2psam3(pqissllistener *l, PQInterface *parent, p3LinkMgr *lm); + + // NetInterface interface +public: + bool connect_parameter(uint32_t type, const std::string &value); + + // pqissl interface +protected: + int Initiate_Connection(); + int net_internal_close(int fd); + + // autoProxyCallback interface +public: + void taskFinished(taskTicket *&ticket); + +private: + bool setupSocket(); + +private: + pqisslSam3State mState; + std::string mI2pAddrB32; + std::string mI2pAddrLong; + +// samSession *mSs; +#ifdef RS_USE_I2P_SAM3_I2PSAM + int mConn; +#endif +#ifdef RS_USE_I2P_SAM3_LIBSAM3 + Sam3Connection *mConn; +#endif +}; + +#endif // PQISSLI2PSAM3_H diff --git a/libretroshare/src/pqi/pqissllistener.h b/libretroshare/src/pqi/pqissllistener.h index 85af2d869..800ca876b 100644 --- a/libretroshare/src/pqi/pqissllistener.h +++ b/libretroshare/src/pqi/pqissllistener.h @@ -186,8 +186,8 @@ public: virtual int finaliseConnection(int fd, SSL *ssl, const RsPeerId& peerId, const sockaddr_storage &raddr); + RS_SET_CONTEXT_DEBUG_LEVEL(2) + private: std::map listenaddr; - - RS_SET_CONTEXT_DEBUG_LEVEL(2) }; diff --git a/libretroshare/src/pqi/pqisslpersongrp.cc b/libretroshare/src/pqi/pqisslpersongrp.cc index 7016a2fa6..16112c744 100644 --- a/libretroshare/src/pqi/pqisslpersongrp.cc +++ b/libretroshare/src/pqi/pqisslpersongrp.cc @@ -46,7 +46,7 @@ static struct RsLog::logInfo pqipersongrpzoneInfo = {RsLog::Default, "pqipersong #endif #include "pqi/pqisslproxy.h" -#include "pqi/pqissli2pbob.h" +#include "pqi/pqissli2psam3.h" pqilistener * pqisslpersongrp::locked_createListener(const struct sockaddr_storage &laddr) { @@ -74,26 +74,26 @@ pqiperson * pqisslpersongrp::locked_createPerson(const RsPeerId& id, pqilistener std::cerr << std::endl; #endif - // Use pqicI2PBOB for I2P - pqiconnect *pqicSOCKSProxy, *pqicI2PBOB; + // Use pqicI2P for I2P + pqiconnect *pqicSOCKSProxy, *pqicI2P; { pqisslproxy *pqis = new pqisslproxy((pqissllistener *) listener, pqip, mLinkMgr); RsSerialiser *rss = new RsSerialiser(); rss->addSerialType(new RsRawSerialiser()); pqicSOCKSProxy = new pqiconnect(pqip, rss, pqis); } - if (rsAutoProxyMonitor::instance()->isEnabled(autoProxyType::I2PBOB)) + + if (rsAutoProxyMonitor::instance()->isEnabled(autoProxyType::I2PSAM3)) { - pqissli2pbob *pqis = new pqissli2pbob((pqissllistener *) listener, pqip, mLinkMgr); + pqissli2psam3 *pqis = new pqissli2psam3((pqissllistener *) listener, pqip, mLinkMgr); RsSerialiser *rss = new RsSerialiser(); rss->addSerialType(new RsRawSerialiser()); - pqicI2PBOB = new pqiconnect(pqip, rss, pqis); + pqicI2P = new pqiconnect(pqip, rss, pqis); } else { - pqicI2PBOB = pqicSOCKSProxy; + pqicI2P = pqicSOCKSProxy; } - /* first select type based on peer */ uint32_t typePeer = mPeerMgr->getHiddenType(id); switch (typePeer) { @@ -101,7 +101,7 @@ pqiperson * pqisslpersongrp::locked_createPerson(const RsPeerId& id, pqilistener pqip -> addChildInterface(PQI_CONNECT_HIDDEN_TOR_TCP, pqicSOCKSProxy); break; case RS_HIDDEN_TYPE_I2P: - pqip -> addChildInterface(PQI_CONNECT_HIDDEN_I2P_TCP, pqicI2PBOB); + pqip -> addChildInterface(PQI_CONNECT_HIDDEN_I2P_TCP, pqicI2P); break; default: /* peer is not a hidden one but we are */ @@ -109,7 +109,7 @@ pqiperson * pqisslpersongrp::locked_createPerson(const RsPeerId& id, pqilistener uint32_t typeOwn = mPeerMgr->getHiddenType(AuthSSL::getAuthSSL()->OwnId()); switch (typeOwn) { case RS_HIDDEN_TYPE_I2P: - pqip -> addChildInterface(PQI_CONNECT_HIDDEN_I2P_TCP, pqicI2PBOB); + pqip -> addChildInterface(PQI_CONNECT_HIDDEN_I2P_TCP, pqicI2P); break; default: /* this case shouldn't happen! */ diff --git a/libretroshare/src/retroshare/rsinit.h b/libretroshare/src/retroshare/rsinit.h index 73fea2684..d26e7ac05 100644 --- a/libretroshare/src/retroshare/rsinit.h +++ b/libretroshare/src/retroshare/rsinit.h @@ -195,7 +195,7 @@ public: /* * Setup Hidden Location; */ - static void SetHiddenLocation(const std::string& hiddenaddress, uint16_t port, bool useBob); + static void SetHiddenLocation(const std::string& hiddenaddress, uint16_t port, bool useI2p); static bool LoadPassword(const std::string& passwd) ; diff --git a/libretroshare/src/rsserver/p3face.h b/libretroshare/src/rsserver/p3face.h index 661cb244f..648e39123 100644 --- a/libretroshare/src/rsserver/p3face.h +++ b/libretroshare/src/rsserver/p3face.h @@ -43,7 +43,7 @@ class p3heartbeat; class p3discovery2; -class p3I2pBob; +class p3I2pSam3; /* GXS Classes - just declare the classes. so we don't have to totally recompile to switch */ @@ -161,8 +161,8 @@ public: p3ChatService *chatSrv; p3StatusService *mStatusSrv; p3GxsTunnelService *mGxsTunnels; -#ifdef RS_USE_I2P_BOB - p3I2pBob *mI2pBob; +#ifdef RS_USE_I2P_SAM3 + p3I2pSam3 *mI2pSam3; #endif // This list contains all threaded services. It will be used to shut them down properly. diff --git a/libretroshare/src/rsserver/rsinit.cc b/libretroshare/src/rsserver/rsinit.cc index 8449a9e3e..864099f01 100644 --- a/libretroshare/src/rsserver/rsinit.cc +++ b/libretroshare/src/rsserver/rsinit.cc @@ -170,7 +170,7 @@ struct RsInitConfig std::string hiddenNodeAddress; uint16_t hiddenNodePort; - bool hiddenNodeI2PBOB; + bool hiddenNodeI2P; /* Logging */ bool haveLogFile; @@ -664,13 +664,13 @@ void RsInit::setAutoLogin(bool autoLogin){ } /* Setup Hidden Location; */ -void RsInit::SetHiddenLocation(const std::string& hiddenaddress, uint16_t port, bool useBob) +void RsInit::SetHiddenLocation(const std::string& hiddenaddress, uint16_t port, bool useI2p) { /* parse the bugger (todo) */ rsInitConfig->hiddenNodeSet = true; rsInitConfig->hiddenNodeAddress = hiddenaddress; rsInitConfig->hiddenNodePort = port; - rsInitConfig->hiddenNodeI2PBOB = useBob; + rsInitConfig->hiddenNodeI2P = useI2p; } @@ -718,6 +718,7 @@ RsGRouter *rsGRouter = NULL ; #endif // def RS_USE_LIBUPNP #include "services/autoproxy/p3i2pbob.h" +#include "services/autoproxy/p3i2psam3.h" #include "services/autoproxy/rsautoproxymonitor.h" #include "services/p3gxsreputation.h" @@ -923,9 +924,9 @@ int RsServer::StartupRetroShare() mNetMgr->setManagers(mPeerMgr, mLinkMgr); rsAutoProxyMonitor *autoProxy = rsAutoProxyMonitor::instance(); -#ifdef RS_USE_I2P_BOB - mI2pBob = new p3I2pBob(mPeerMgr); - autoProxy->addProxy(autoProxyType::I2PBOB, mI2pBob); +#ifdef RS_USE_I2P_SAM3 + mI2pSam3 = new p3I2pSam3(mPeerMgr); + autoProxy->addProxy(autoProxyType::I2PSAM3, mI2pSam3); #endif //load all the SSL certs as friends @@ -1655,8 +1656,9 @@ int RsServer::StartupRetroShare() mConfigMgr->addConfiguration("wire.cfg", wire_ns); #endif #endif //RS_ENABLE_GXS -#ifdef RS_USE_I2P_BOB - mConfigMgr->addConfiguration("I2PBOB.cfg", mI2pBob); +#ifdef RS_USE_I2P_SAM3 + // to make migration easiert, SAM will use BOBs configuration, as they are compatible / the same. + mConfigMgr->addConfiguration("I2PBOB.cfg", mI2pSam3); #endif mPluginsManager->addConfigurations(mConfigMgr) ; @@ -1709,34 +1711,33 @@ int RsServer::StartupRetroShare() { std::cout << "RsServer::StartupRetroShare setting up hidden locations" << std::endl; - if (rsInitConfig->hiddenNodeI2PBOB) { - std::cout << "RsServer::StartupRetroShare setting up BOB" << std::endl; + if (rsInitConfig->hiddenNodeI2P) { + std::cout << "RsServer::StartupRetroShare setting up SAMv3" << std::endl; // we need a local port! mNetMgr->checkNetAddress(); // add i2p proxy - // bob will use this address sockaddr_storage i2pInstance; sockaddr_storage_ipv4_aton(i2pInstance, rsInitConfig->hiddenNodeAddress.c_str()); mPeerMgr->setProxyServerAddress(RS_HIDDEN_TYPE_I2P, i2pInstance); std::string addr; // will be set by auto proxy service - uint16_t port = rsInitConfig->hiddenNodePort; // unused by bob + uint16_t port; // unused by SAM - bool r = autoProxy->initialSetup(autoProxyType::I2PBOB, addr, port); + bool r = autoProxy->initialSetup(autoProxyType::I2PSAM3, addr, port); if (r && !addr.empty()) { mPeerMgr->setupHiddenNode(addr, port); - // now enable bob - bobSettings bs; - autoProxy->taskSync(autoProxyType::I2PBOB, autoProxyTask::getSettings, &bs); - bs.enable = true; - autoProxy->taskSync(autoProxyType::I2PBOB, autoProxyTask::setSettings, &bs); + // now enable SAM + samSettings ss; + autoProxy->taskSync(autoProxyType::I2PSAM3, autoProxyTask::getSettings, &ss); + ss.enable = true; + autoProxy->taskSync(autoProxyType::I2PSAM3, autoProxyTask::setSettings, &ss); } else { std::cerr << "RsServer::StartupRetroShare failed to receive keys" << std::endl; - /// TODO add notify for failed bob setup + /// TODO add notify for failed i2p setup } } else { mPeerMgr->setupHiddenNode(rsInitConfig->hiddenNodeAddress, rsInitConfig->hiddenNodePort); @@ -1758,19 +1759,17 @@ int RsServer::StartupRetroShare() if (rsInitConfig->hiddenNodeSet) { // newly created location // mNetMgr->checkNetAddress() will setup ports for us + +#if 0 // this was used for BOB but is not requires for SAMv3 // trigger updates for auto proxy services std::vector types; - - // i2p bob need to rebuild its command map - types.push_back(autoProxyType::I2PBOB); - rsAutoProxyMonitor::taskSync(types, autoProxyTask::reloadConfig); +#endif } /**************************************************************************/ /* startup (stuff dependent on Ids/peers is after this point) */ /**************************************************************************/ - autoProxy->startAll(); pqih->init_listener(); @@ -1803,8 +1802,8 @@ int RsServer::StartupRetroShare() /**************************************************************************/ // auto proxy threads -#ifdef RS_USE_I2P_BOB - startServiceThread(mI2pBob, "I2P-BOB"); +#ifdef RS_USE_I2P_SAM3 + startServiceThread(mI2pSam3, "I2P-SAM3"); #endif #ifdef RS_ENABLE_GXS diff --git a/libretroshare/src/services/autoproxy/p3i2psam3.cpp b/libretroshare/src/services/autoproxy/p3i2psam3.cpp new file mode 100644 index 000000000..215f23964 --- /dev/null +++ b/libretroshare/src/services/autoproxy/p3i2psam3.cpp @@ -0,0 +1,894 @@ +#include "p3i2psam3.h" + +#ifdef RS_USE_I2P_SAM3_LIBSAM3 +#include +#endif +#ifdef RS_USE_I2P_SAM3_I2PSAM +#include "util/i2psam.h" +#endif + +#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; + +RS_SET_CONTEXT_DEBUG_LEVEL(4) + +// copy from i2psam.cpp +//#define I2P_DESTINATION_SIZE 516 + +static void inline doSleep(std::chrono::duration> timeToSleepMS) { + std::this_thread::sleep_for(timeToSleepMS); +} + +p3I2pSam3::p3I2pSam3(p3PeerMgr *peerMgr) : + mConfigLoaded(false), mPeerMgr(peerMgr), mPending(), 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 = 1; +} + +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_DBG("overwriting keys!"); + + bool success = generateKey(mSetting.address.publicKey, mSetting.address.privateKey); + + if (!success) { + RS_DBG("failed to retrieve keys"); + return false; + } else { + std::string s, c; + i2p::getKeyTypes(mSetting.address.publicKey, s, c); + RS_INFO("received key", s, c); + + IndicateConfigChanged(); + } + + addr = mSetting.address.base32 = i2p::keyToBase32Addr(mSetting.address.publicKey); + 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(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(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(ticket->data); + updateSettings_locked(); + } + + // finish task + rsAutoProxyMonitor::taskDone(ticket, autoProxyStatus::ok); + break; + case autoProxyTask::getErrorInfo: +#ifdef RS_USE_I2P_SAM3_LIBSAM3 + *static_cast(ticket->data) = mSetting.session->error; + rsAutoProxyMonitor::taskDone(ticket, autoProxyStatus::ok); +#else + rsAutoProxyMonitor::taskError(ticket); +#endif + 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(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); +#ifdef RS_USE_I2P_SAM3_I2PSAM + // artificially delay following operations as i2psam uses time(null) as rng seed + doSleep(std::chrono::seconds(1)); +#endif + break; + + case autoProxyTask::proxyStatusCheck: + { + // TODO better detection of status + bool ok; +#ifdef RS_USE_I2P_SAM3_I2PSAM + ok = !mSetting.session->isSick(); +#endif +#ifdef RS_USE_I2P_SAM3_LIBSAM3 + ok = !!mSetting.session->fd; + ok &= !!mSetting.session->fwd_fd; +#endif // RS_USE_I2P_SAM3_LIBSAM3 + *static_cast(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 &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 &load) +{ + RS_DBG4(); + + std::string priv; + priv.clear(); + + for(std::list::const_iterator it = load.begin(); it!=load.end(); ++it) { + RsConfigKeyValueSet *vitem = dynamic_cast(*it); + if(vitem != NULL) { + RS_STACK_MUTEX(mLock); + for(std::list::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 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)); + } + + std::string paramsStr; + for (auto &&p : params) + paramsStr.append(p + " "); + // keep trailing space for easier extending when necessary + +#ifdef RS_USE_I2P_SAM3_I2PSAM + if(mSetting.session) { + RS_STACK_MUTEX(mLock); + + delete mSetting.session; // stopForwardingAll(); is called in destructor + mSetting.session = nullptr; + } + + SAM::StreamSession *session; + + if(!mSetting.address.privateKey.empty()) { + Dbg3() << __PRETTY_FUNCTION__ << " with destination" << std::endl; + session = new SAM::StreamSession(nick, SAM_DEFAULT_ADDRESS, SAM_DEFAULT_PORT, mSetting.address.privateKey, paramsStr); + } else { + Dbg3() << __PRETTY_FUNCTION__ << " without destination" << std::endl; + session = new SAM::StreamSession(nick, SAM_DEFAULT_ADDRESS, SAM_DEFAULT_PORT, SAM_GENERATE_MY_DESTINATION, paramsStr, "DSA_SHA1"); + } + + if (!session || session->isSick()) { + return false; + } + + /* + * i2psam is sometimes unable to reliable read the public key, which is crucial to base32 address generation + * (due to the fact that is assumes it's length wrongly) + * + * The following are attempts to reliable receive our public key + */ + + const auto dest = session->getMyDestination(); +// auto copy = dest; + std::string pubKey1 = dest.pub; + std::string pubKey2(dest.pub); + i2p::validatePubkeyFromPrivKey(pubKey2, dest.priv); + + /* + * pubkeys: + * 1: from initial call (== dest.pub) + * 2: from parsing (was == dest.pub + */ + Dbg3() << __PRETTY_FUNCTION__ << " figuring out our pubKey: p1 " << pubKey1 << std::endl; + Dbg3() << __PRETTY_FUNCTION__ << " figuring out our pubKey: p2 " << pubKey2 << std::endl; + if (pubKey1 == pubKey2) { + // unchanged + Dbg3() << __PRETTY_FUNCTION__ << " figuring out our pubKey: p1 == p2" << std::endl; + } else { + // parsed pub key is different, prefer parsed one + pubKey1 = pubKey2; + Dbg3() << __PRETTY_FUNCTION__ << " figuring out our pubKey: p1 != p2" << std::endl; + } + SAM::FullDestination dest2(pubKey1, dest.priv, true); + + // populate settings + RS_STACK_MUTEX(mLock); + mSetting.session = session; + if (!mSetting.address.publicKey.empty() && mSetting.address.publicKey != dest2.pub) + // This should be ok for non hidden locations. This should be a problem for hidden i2p locations... + RsDbg() << __PRETTY_FUNCTION__ << " public key changed! Yet unsure if this is ok or a problem" << std::endl; + mSetting.address.publicKey = dest2.pub; + mSetting.address.base32 = i2p::keyToBase32Addr(dest2.pub); + // do not overwrite the private key, if any!! + +#endif +#ifdef RS_USE_I2P_SAM3_LIBSAM3 + int ret; + + if (mSetting.session) { + stopSession(); + } + + auto session = new 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 = sam3CreateSilentSession(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 = sam3CreateSilentSession(session, SAM3_HOST_DEFAULT, SAM3_PORT_DEFAULT, SAM3_DESTINATION_TRANSIENT, Sam3SessionType::SAM3_SESSION_STREAM, Sam3SigType::EdDSA_SHA512_Ed25519, paramsStr.c_str()); + } + } + + if (ret != 0) { + delete 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!! +#endif + + 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); + +#ifdef RS_USE_I2P_SAM3_I2PSAM + auto ret = mSetting.session->forward(sockaddr_storage_iptostring(ps.localaddr).c_str(), sockaddr_storage_port(ps.localaddr), true); + if (!ret.isOk) + RsDbg() << __PRETTY_FUNCTION__ << " forward failed" << std::endl; + else + Dbg2() << __PRETTY_FUNCTION__ << " forward successfull" << std::endl; + + return ret.isOk; +#endif +#ifdef RS_USE_I2P_SAM3_LIBSAM3 + RS_STACK_MUTEX(mLockSam3Access); + + 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; + } +#endif + return true; +} + +void p3I2pSam3::stopSession() +{ + RS_DBG4(); + + { + RS_STACK_MUTEX(mLock); + if (!mSetting.session) + return; +#ifdef RS_USE_I2P_SAM3_I2PSAM + // TODO cleanup sockets + delete mSetting.session; // will stop forwarding, too +#endif +#ifdef RS_USE_I2P_SAM3_LIBSAM3 + // swap connections + mInvalidConnections = mValidConnections; + mValidConnections.clear(); + + RS_STACK_MUTEX(mLockSam3Access); + sam3CloseSession(mSetting.session); + delete mSetting.session; +#endif + + mSetting.session = nullptr; + mState = samStatus::samState::offline; + } + + // At least i2pd doesn't like to instantaniously stop and (re)start a session, wait here just a little bit. + // Not ideal but does the trick. + // (This happens when using the "restart" button in the settings.) + doSleep(std::chrono::seconds(10)); +} + +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(); + +#ifdef RS_USE_I2P_SAM3_I2PSAM + auto ss = new SAM::StreamSession("RS-destgen"); + auto ret = ss->destGenerate(); + if (!ret.isOk) + return false; + + auto dest = ret.value; + pub = std::string(dest.pub); + priv = std::string(dest.priv); + +#endif +#ifdef RS_USE_I2P_SAM3_LIBSAM3 + // 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); +#endif + + 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(ticket->data); + if (addr->base32.empty()) { + RS_ERR("lookupKey: called with empty address"); + rsAutoProxyMonitor::taskError(ticket); + return; + } +#ifdef RS_USE_I2P_SAM3_I2PSAM + auto sam = mSetting.session; + RsThread::async([ticket, sam]() +#endif +#ifdef RS_USE_I2P_SAM3_LIBSAM3 + RsThread::async([ticket]() +#endif + { + auto addr = static_cast(ticket->data); + +#ifdef RS_USE_I2P_SAM3_I2PSAM + auto r = sam->namingLookup(addr->base32); + if (!r.isOk) { + // get error + RsDbg() << __PRETTY_FUNCTION__ << " key: " << addr->base32 << std::endl; + RsDbg() << __PRETTY_FUNCTION__ << " got error!" << std::endl; + rsAutoProxyMonitor::taskError(ticket); + } else { + addr->publicKey = r.value; + rsAutoProxyMonitor::taskDone(ticket, autoProxyStatus::ok); + Dbg1() << __PRETTY_FUNCTION__ << " success " << std::endl; + } +#endif +#ifdef RS_USE_I2P_SAM3_LIBSAM3 + // 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"); + } +#endif + }); +} + +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(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(ticket->data); +#ifdef RS_USE_I2P_SAM3_I2PSAM + auto r = this->mSetting.session->connect(wrapper->address.publicKey.c_str(), false); // silent=true is broken! + if (!r.isOk) { + // get error + RsDbg() << __PRETTY_FUNCTION__ << " got error!" << std::endl; + rsAutoProxyMonitor::taskError(ticket); + } else { + // extract socket + wrapper->socket = r.value.get()->release(); + Dbg1() << __PRETTY_FUNCTION__ << " success " << std::endl; + rsAutoProxyMonitor::taskDone(ticket, autoProxyStatus::ok); + } +#endif +#ifdef RS_USE_I2P_SAM3_LIBSAM3 + struct Sam3Connection *connection; + { + auto l = this->mLockSam3Access; + RS_STACK_MUTEX(l); + 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; + { + auto l = this->mLockSam3Access; + RS_STACK_MUTEX(l); + this->mValidConnections.push_back(connection); + } + RS_DBG1("success"); + rsAutoProxyMonitor::taskDone(ticket, autoProxyStatus::ok); + } +#endif + }); +} + +void p3I2pSam3::closeConnection(taskTicket *ticket) +{ + Sam3Connection *con = static_cast(ticket->data); + + if (mState == samStatus::samState::offline || !mSetting.session) { + // no session found, sam was likel stopped + } else { + RS_STACK_MUTEX(mLock); + + bool callClose = true; + // search in current connections + auto it = std::find(mValidConnections.begin(), mValidConnections.end(), con); + if (it != mValidConnections.end()) { + mValidConnections.erase(it); + } else { + // search in old connections + it = std::find(mInvalidConnections.begin(), mInvalidConnections.end(), con); + if (it != mInvalidConnections.end()) { + // old connection, just ignore. *should* be freed already + callClose = false; + con = nullptr; + } else { + // weird + RS_WARN("could'n find connection!"); + + // best thing we can do here + callClose = false; + con = nullptr; + } + } + + if (callClose) { + RS_STACK_MUTEX(mLockSam3Access); + sam3CloseConnection(con); + con = nullptr; // freed by above call + } + } + + if (con) { + delete con; + con = 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 +} diff --git a/libretroshare/src/services/autoproxy/p3i2psam3.h b/libretroshare/src/services/autoproxy/p3i2psam3.h new file mode 100644 index 000000000..bbf13c143 --- /dev/null +++ b/libretroshare/src/services/autoproxy/p3i2psam3.h @@ -0,0 +1,128 @@ +#ifndef P3I2PSAM3_H +#define P3I2PSAM3_H + +#include +#include + +#include "services/autoproxy/rsautoproxymonitor.h" +#include "pqi/p3cfgmgr.h" +#include "util/i2pcommon.h" +#include "util/rsthreads.h" + +/* + * This class implements I2P SAMv3 (Simple Anonymous Messaging) to allow RS + * to automatically setup tunnel to and from I2P. + * SAMv3 is a simple text-based interface: https://geti2p.net/de/docs/api/samv3 + * + * For the actual SAM commands / low level stuff libsam3 (https://github.com/i2p/libsam3) + * is used with some minor adjustments, for exmaple, the FORWARD session is always silent. + * + * SAM in a nutshell works like this: + * 1) setup main/control session which configures everything (destination ID, tunnel number, hops number, and so on) + * 2) setup a forward session, so that I2P will establish a connection to RS for each incoming connection to our i2p destination + * 3a) query/lookup the destination (public key) for a given i2p address + * 3b) connect to the given destination + * + * An established connection (both incoming or outgoing) are then handed over to RS. + * The lifetime of a session (and its subordinates connections) is bound to their tcp socket. When the socket closes, the session is closed, too. + * + */ + +class p3PeerMgr; + +// typedef samSession is used to unify access to the session independent of the underlying library +#ifdef RS_USE_I2P_SAM3_I2PSAM +namespace SAM { +class StreamSession; +class I2pSocket; +} + +typedef SAM::StreamSession samSession; +#endif +#ifdef RS_USE_I2P_SAM3_LIBSAM3 +class Sam3Session; +class Sam3Connection; + +typedef Sam3Session samSession; +#endif + +struct samSettings : i2p::settings { + samSession *session; +}; + +struct samEstablishConnectionWrapper { + i2p::address address; +#ifdef RS_USE_I2P_SAM3_I2PSAM + int socket; +#endif +#ifdef RS_USE_I2P_SAM3_LIBSAM3 + Sam3Connection *connection; +#endif +}; + +struct samStatus { + std::string sessionName; + enum samState { + offline, + connectSession, + connectForward, + online + } state; // the name is kinda redundant ... +}; + +class p3I2pSam3 : public RsTickingThread, public p3Config, public autoProxyService +{ +public: + p3I2pSam3(p3PeerMgr *peerMgr); + + // autoProxyService interface +public: + bool isEnabled(); + bool initialSetup(std::string &addr, uint16_t &port); + void processTaskAsync(taskTicket *ticket); + void processTaskSync(taskTicket *ticket); + + // RsTickingThread interface +public: + void threadTick(); /// @see RsTickingThread + + // p3Config interface +protected: + RsSerialiser *setupSerialiser(); + bool saveList(bool &cleanup, std::list &); + bool loadList(std::list &load); + +private: + bool startSession(); + bool startForwarding(); + void stopSession(); + void stopForwarding(); + + bool generateKey(std::string &pub, std::string &priv); + void lookupKey(taskTicket *ticket); + void establishConnection(taskTicket *ticket); + void closeConnection(taskTicket *ticket); + void updateSettings_locked(); + + bool mConfigLoaded; + + samSettings mSetting; + p3PeerMgr *mPeerMgr; + std::queue mPending; + + // Used to report the state to the gui + // (Since the create session call/will can block and there is no easy way from outside the main thread to see + // what is going on, it is easier to store the current state in an extra variable independen from the main thread) + samStatus::samState mState; + + // used to keep track of connections, libsam3 does it internally but it can be unreliable since pointers are shared + std::list mValidConnections, mInvalidConnections; + + // mutex + RsMutex mLock; +#ifdef RS_USE_I2P_SAM3_LIBSAM3 + RsMutex mLockSam3Access; // libsam3 is not thread safe! (except for key lookup) +#endif +}; + +#endif // P3I2PSAM3_H diff --git a/libretroshare/src/services/autoproxy/rsautoproxymonitor.cc b/libretroshare/src/services/autoproxy/rsautoproxymonitor.cc index d58c871e3..b2d71571e 100644 --- a/libretroshare/src/services/autoproxy/rsautoproxymonitor.cc +++ b/libretroshare/src/services/autoproxy/rsautoproxymonitor.cc @@ -329,14 +329,22 @@ autoProxyService *rsAutoProxyMonitor::lookUpService(autoProxyType::autoProxyType bool rsAutoProxyMonitor::isAsyncTask(autoProxyTask::autoProxyTask_enum t) { + // Explicit list all values, so that missing ones will be detected by the compiler. switch (t) { - case autoProxyTask::start: - case autoProxyTask::stop: - case autoProxyTask::receiveKey: + case autoProxyTask::start: [[fallthrough]]; + case autoProxyTask::stop: [[fallthrough]]; + case autoProxyTask::receiveKey: [[fallthrough]]; + case autoProxyTask::lookupKey: [[fallthrough]]; + case autoProxyTask::establishConnection: [[fallthrough]]; + case autoProxyTask::closeConnection: return true; - break; - default: - break; + case autoProxyTask::status: [[fallthrough]]; + case autoProxyTask::getSettings: [[fallthrough]]; + case autoProxyTask::setSettings: [[fallthrough]]; + case autoProxyTask::getErrorInfo: [[fallthrough]]; + case autoProxyTask::reloadConfig: [[fallthrough]]; + case autoProxyTask::proxyStatusCheck: + return false; } return false; } diff --git a/libretroshare/src/services/autoproxy/rsautoproxymonitor.h b/libretroshare/src/services/autoproxy/rsautoproxymonitor.h index d9a4c16aa..e08692947 100644 --- a/libretroshare/src/services/autoproxy/rsautoproxymonitor.h +++ b/libretroshare/src/services/autoproxy/rsautoproxymonitor.h @@ -31,23 +31,27 @@ class autoProxyCallback; namespace autoProxyType { enum autoProxyType_enum { - I2PBOB +// I2PBOB, + I2PSAM3 }; } namespace autoProxyTask { enum autoProxyTask_enum { /* async tasks */ - start, ///< start up proxy - stop, ///< shut down proxy - receiveKey, ///< renew proxy key (if any) - proxyStatusCheck, ///< use to check if the proxy is still running + start, ///< start up proxy + stop, ///< shut down proxy + receiveKey, ///< renew proxy key (if any) + lookupKey, ///< look up a base32 addr + proxyStatusCheck, ///< use to check if the proxy is still running + establishConnection, ///< create a connection to a given public key or base32 address + closeConnection, ///< closes a connection /* sync tasks */ - status, ///< get status from auto proxy - getSettings, ///< get setting from auto proxy - setSettings, ///< set setting of auto proxy - reloadConfig, ///< signal config reload/rebuild - getErrorInfo ///< get error information from auto proxy + status, ///< get status from auto proxy + getSettings, ///< get setting from auto proxy + setSettings, ///< set setting of auto proxy + reloadConfig, ///< signal config reload/rebuild + getErrorInfo ///< get error information from auto proxy }; } diff --git a/libretroshare/src/use_libretroshare.pri b/libretroshare/src/use_libretroshare.pri index b76c4a5da..63009745f 100644 --- a/libretroshare/src/use_libretroshare.pri +++ b/libretroshare/src/use_libretroshare.pri @@ -90,6 +90,15 @@ rs_broadcast_discovery { win32-g++|win32-clang-g++:dLibs *= wsock32 } +rs_sam3_libsam3 { + LIBSAM3_SRC_PATH=$$clean_path($${RS_SRC_PATH}/supportlibs/libsam3/) + LIBSAM3_BUILD_PATH=$$clean_path($${RS_BUILD_PATH}/supportlibs/libsam3/) + INCLUDEPATH *= $$clean_path($${LIBSAM3_SRC_PATH}/src/libsam3/) + DEPENDPATH *= $$clean_path($${LIBSAM3_BUILD_PATH}) + QMAKE_LIBDIR *= $$clean_path($${LIBSAM3_BUILD_PATH}) + LIBS *= -L$$clean_path($${LIBSAM3_BUILD_PATH}) -lsam3 +} + static { sLibs *= $$mLibs } else { diff --git a/libretroshare/src/util/i2pcommon.cpp b/libretroshare/src/util/i2pcommon.cpp index bca7ae110..0733a807d 100644 --- a/libretroshare/src/util/i2pcommon.cpp +++ b/libretroshare/src/util/i2pcommon.cpp @@ -50,7 +50,7 @@ std::string publicKeyFromPrivate(std::string const &priv) * https://geti2p.net/spec/common-structures#keysandcert * https://geti2p.net/spec/common-structures#certificate */ - if (priv.length() < 884) // base64 ( = 663 bytes = KeyCert + priv Keys) + if (priv.empty() || priv.length() < 884) // base64 ( = 663 bytes = KeyCert + priv Keys) return std::string(); // creat a copy to work on, need to convert it to standard base64 @@ -163,6 +163,9 @@ std::string publicKeyFromPrivate(std::string const &priv) bool getKeyTypes(const std::string &key, std::string &signingKey, std::string &cryptoKey) { + if (key.length() < 522) // base64 (391 bytes = 384 bytes + 7 bytes = KeysAndCert + Certificate) + return false; + // creat a copy to work on, need to convert it to standard base64 auto key_copy(key); std::replace(key_copy.begin(), key_copy.end(), '~', '/'); @@ -225,7 +228,7 @@ bool getKeyTypes(const std::string &key, std::string &signingKey, std::string &c // now convert to string (this would be easier with c++17) #define HELPER(a, b, c) \ case static_cast::type>(a::c): \ - b = "c"; \ + b = #c; \ break; switch (signingKeyType) { diff --git a/libretroshare/src/util/i2pcommon.h b/libretroshare/src/util/i2pcommon.h index e41b61010..0a76fa080 100644 --- a/libretroshare/src/util/i2pcommon.h +++ b/libretroshare/src/util/i2pcommon.h @@ -210,7 +210,7 @@ std::string publicKeyFromPrivate(const std::string &priv); /** * @brief getKeyTypes returns the name of the utilized algorithms used by the key - * @param key public key (private works, too) + * @param key public key * @param signingKey name of the signing key, e.g. DSA_SHA1 * @param cryptoKey name of the crpyto key, e.g. ElGamal * @return true on success, false otherwise diff --git a/plugins/plugins.pro b/plugins/plugins.pro index 209b82898..7ecbc32c7 100644 --- a/plugins/plugins.pro +++ b/plugins/plugins.pro @@ -19,5 +19,5 @@ TEMPLATE = subdirs SUBDIRS += \ - VOIP \ + VOIP \ FeedReader diff --git a/retroshare-gui/src/gui/GenCertDialog.cpp b/retroshare-gui/src/gui/GenCertDialog.cpp index e106146fb..68ff05c42 100644 --- a/retroshare-gui/src/gui/GenCertDialog.cpp +++ b/retroshare-gui/src/gui/GenCertDialog.cpp @@ -539,12 +539,12 @@ void GenCertDialog::genPerson() std::string hl = ui.hiddenaddr_input->text().toStdString(); uint16_t port = ui.hiddenport_spinBox->value(); - bool useBob = ui.cbUseBob->isChecked(); + bool useI2p = ui.cbUseBob->isChecked(); - if (useBob && hl.empty()) + if (useI2p && hl.empty()) hl = "127.0.0.1"; - RsInit::SetHiddenLocation(hl, port, useBob); /* parses it */ + RsInit::SetHiddenLocation(hl, port, useI2p); /* parses it */ } diff --git a/retroshare-gui/src/gui/settings/ServerPage.ui b/retroshare-gui/src/gui/settings/ServerPage.ui index 5ef7308b1..9d506bf38 100755 --- a/retroshare-gui/src/gui/settings/ServerPage.ui +++ b/retroshare-gui/src/gui/settings/ServerPage.ui @@ -6,8 +6,8 @@ 0 0 - 712 - 502 + 726 + 579 @@ -908,9 +908,9 @@ If you have issues connecting over Tor check the Tor logs too. - + - Automatic I2P/BOB + Automatic I2P @@ -918,7 +918,7 @@ If you have issues connecting over Tor check the Tor logs too. - Enable I2P BOB - changing this requires a restart to fully take effect + Enable I2P SAMv3 - changing this requires a restart to fully take effect @@ -950,7 +950,7 @@ If you have issues connecting over Tor check the Tor logs too. - I2P Basic Open Bridge + I2P Simple Anonymous Messaging @@ -990,6 +990,12 @@ If you have issues connecting over Tor check the Tor logs too. + + false + + + Not required for SAMv3 + 10 @@ -1016,7 +1022,7 @@ If you have issues connecting over Tor check the Tor logs too. <html><head/><body><p>This led is green when the port listen on the left is active on your computer. It does not</p><p>mean that your Retroshare traffic transits though I2P. It will do so only if </p><p>you connect to Hidden nodes, or if you are running a Hidden node yourself.</p></body></html> - BOB accessible + SAM accessible @@ -1304,6 +1310,13 @@ If you have issues connecting over Tor check the Tor logs too. + + + + <key info> + + + @@ -1359,7 +1372,7 @@ If you have issues connecting over Tor check the Tor logs too. - BOB status + SAM status diff --git a/retroshare.pri b/retroshare.pri index 2c2201ef3..825d5d49e 100644 --- a/retroshare.pri +++ b/retroshare.pri @@ -140,11 +140,6 @@ rs_macos10.15:CONFIG -= rs_macos10.11 CONFIG *= no_rs_jsonapi rs_jsonapi:CONFIG -= no_rs_jsonapi -# Disable i2p BOB support for automatically setting up an i2p tunnel for RS -# "CONFIG+=no_rs_bob" -CONFIG *= rs_bob -no_rs_bob:CONFIG -= rs_bob - # To enable channel indexing append the following assignation to qmake command # line "CONFIG+=rs_deep_channels_index" CONFIG *= no_rs_deep_channels_index @@ -209,6 +204,10 @@ no_rs_dh_init_check:CONFIG -= rs_dh_init_check # many exported symbols. retroshare_plugins:win32:CONFIG *= libretroshare_shared +CONFIG+=rs_sam3 +CONFIG+=rs_sam3_libsam3 +#CONFIG+=rs_sam3_i2psam + # Specify host precompiled jsonapi-generator path, appending the following # assignation to qmake command line # 'JSONAPI_GENERATOR_EXE=/myBuildDir/jsonapi-generator'. Required for JSON API @@ -559,10 +558,6 @@ rs_webui { DEFINES *= RS_WEBUI } -rs_bob { - DEFINES *= RS_USE_I2P_BOB -} - rs_deep_channels_index:DEFINES *= RS_DEEP_CHANNEL_INDEX rs_deep_files_index:DEFINES *= RS_DEEP_FILES_INDEX @@ -576,6 +571,14 @@ rs_broadcast_discovery:DEFINES *= RS_BROADCAST_DISCOVERY no_rs_dh_init_check:DEFINES *= RS_DISABLE_DIFFIE_HELLMAN_INIT_CHECK +rs_sam3: { + DEFINES *= RS_USE_I2P_SAM3 + # this allows a downgrade from a SAMv3 build to a BOB build, can be removed in the future + DEFINES *= RS_I2P_SAM3_BOB_COMPAT +} +rs_sam3_libsam3: DEFINES *= RS_USE_I2P_SAM3_LIBSAM3 +rs_sam3_i2psam: DEFINES *= RS_USE_I2P_SAM3_I2PSAM + debug { rs_mutex_debug:DEFINES *= RS_MUTEX_DEBUG diff --git a/supportlibs/libsam3/src/libsam3/libsam3.c b/supportlibs/libsam3/src/libsam3/libsam3.c index 367949a39..476372a0b 100644 --- a/supportlibs/libsam3/src/libsam3/libsam3.c +++ b/supportlibs/libsam3/src/libsam3/libsam3.c @@ -26,6 +26,10 @@ #include #include +#ifdef WINDOWS_SYS +#include +#endif // WINDOWS_SYS + //////////////////////////////////////////////////////////////////////////////// int libsam3_debug = 0; @@ -100,6 +104,11 @@ int sam3tcpConnectIP(uint32_t ip, int port) { } } // + // Set this for all outgoing SAM connections. Most SAM commands should be answered rather fast except CREATE SESSION maybe. + // This should be enough to let SAM establish a session. + sam3tcpSetTimeoutSend(fd, 5 * 60 * 1000); + sam3tcpSetTimeoutReceive(fd, 5 * 60 * 1000); + // setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)); // if (connect(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) < 0) { @@ -152,8 +161,13 @@ int sam3tcpConnect(const char *hostname, int port, uint32_t *ip) { // <0: error; 0: ok int sam3tcpDisconnect(int fd) { if (fd >= 0) { - shutdown(fd, SHUT_RDWR); - return close(fd); +#ifndef WINDOWS_SYS // ie UNIX + shutdown(fd, SHUT_RDWR); + return close(fd); +#else + return closesocket(fd); +#endif + } // return -1;