/** * Copyright (c) 2019-2022 polistern * Copyright (c) 2017 The I2P Project * Copyright (c) 2013-2015 The Anoncoin Core developers * Copyright (c) 2012-2013 giv * * Distributed under the MIT software license, see the accompanying * file LICENSE or http://www.opensource.org/licenses/mit-license.php. * * See full documentation about SAM at http://www.i2p2.i2p/samv3.html */ #ifndef I2PSAM_H__ #define I2PSAM_H__ #define SAM_BUFSIZE 8192 #define SAM_DEFAULT_ADDRESS "127.0.0.1" #define SAM_DEFAULT_PORT_TCP 7656 #define SAM_DEFAULT_PORT_UDP 7655 #define SAM_DEFAULT_CLIENT_TCP 7666 #define SAM_DEFAULT_CLIENT_UDP 7667 #define SAM_GENERATE_MY_DESTINATION "TRANSIENT" #define SAM_MY_NAME "ME" #define SAM_DEFAULT_I2P_OPTIONS SAM_NAME_I2CP_LEASESET_ENC_TYPE "=" SAM_DEFAULT_I2CP_LEASESET_ENC_TYPE // i2cp.leaseSetEncType=0,4 #define SAM_SIGNATURE_TYPE "EdDSA_SHA512_Ed25519" #define SAM_NAME_INBOUND_QUANTITY "inbound.quantity" #define SAM_DEFAULT_INBOUND_QUANTITY 3 // Three tunnels is default now #define SAM_NAME_INBOUND_LENGTH "inbound.length" #define SAM_DEFAULT_INBOUND_LENGTH 3 // Three jumps is default now #define SAM_NAME_INBOUND_LENGTHVARIANCE "inbound.lengthVariance" #define SAM_DEFAULT_INBOUND_LENGTHVARIANCE 0 #define SAM_NAME_INBOUND_BACKUPQUANTITY "inbound.backupQuantity" #define SAM_DEFAULT_INBOUND_BACKUPQUANTITY 1 // One backup tunnel #define SAM_NAME_INBOUND_ALLOWZEROHOP "inbound.allowZeroHop" #define SAM_DEFAULT_INBOUND_ALLOWZEROHOP true #define SAM_NAME_INBOUND_IPRESTRICTION "inbound.IPRestriction" #define SAM_DEFAULT_INBOUND_IPRESTRICTION 2 #define SAM_NAME_OUTBOUND_QUANTITY "outbound.quantity" #define SAM_DEFAULT_OUTBOUND_QUANTITY 3 #define SAM_NAME_OUTBOUND_LENGTH "outbound.length" #define SAM_DEFAULT_OUTBOUND_LENGTH 3 #define SAM_NAME_OUTBOUND_LENGTHVARIANCE "outbound.lengthVariance" #define SAM_DEFAULT_OUTBOUND_LENGTHVARIANCE 0 #define SAM_NAME_OUTBOUND_BACKUPQUANTITY "outbound.backupQuantity" #define SAM_DEFAULT_OUTBOUND_BACKUPQUANTITY 1 #define SAM_NAME_OUTBOUND_ALLOWZEROHOP "outbound.allowZeroHop" #define SAM_DEFAULT_OUTBOUND_ALLOWZEROHOP true #define SAM_NAME_OUTBOUND_IPRESTRICTION "outbound.IPRestriction" #define SAM_DEFAULT_OUTBOUND_IPRESTRICTION 2 #define SAM_NAME_OUTBOUND_PRIORITY "outbound.priority" #define SAM_DEFAULT_OUTBOUND_PRIORITY #define SAM_NAME_I2CP_LEASESET_ENC_TYPE "i2cp.leaseSetEncType" #define SAM_DEFAULT_I2CP_LEASESET_ENC_TYPE "0,4" // Define this, if you want more of the original standard output diagnostics //#define DEBUG_ON_STDOUT #ifdef __cplusplus // __cplusplus #include "compat.h" #include #include #include #include #include #include #include #include #include namespace SAM { typedef u_int SOCKET; static void print_error(const std::string &err) { #ifdef DEBUG_ON_STDOUT #ifdef WIN32 std::cout << err << "(" << WSAGetLastError() << ")" << std::endl; #else // WIN32 std::cout << err << "(" << errno << ")" << std::endl; #endif // WIN32 #endif // DEBUG_ON_STDOUT } class Message { public: enum SessionStyle { sssStream, sssDatagram, sssRaw }; enum eStatus { OK, EMPTY_ANSWER, CLOSED_SOCKET, CANNOT_PARSE_ERROR, /** The destination is already in use * * -> SESSION CREATE ... * <- SESSION STATUS RESULT=DUPLICATED_DEST */ DUPLICATED_DEST, /** * The nickname is already associated with a session * * -> SESSION CREATE ... * <- SESSION STATUS RESULT=DUPLICATED_ID */ DUPLICATED_ID, /** * A generic I2P error (e.g. I2CP disconnection, etc.) * * -> HELLO VERSION ... * <- HELLO REPLY RESULT=I2P_ERROR MESSAGE={$message} * * -> SESSION CREATE ... * <- SESSION STATUS RESULT=I2P_ERROR MESSAGE={$message} * * -> STREAM CONNECT ... * <- STREAM STATUS RESULT=I2P_ERROR MESSAGE={$message} * * -> STREAM ACCEPT ... * <- STREAM STATUS RESULT=I2P_ERROR MESSAGE={$message} * * -> STREAM FORWARD ... * <- STREAM STATUS RESULT=I2P_ERROR MESSAGE={$message} * * -> NAMING LOOKUP ... * <- NAMING REPLY RESULT=INVALID_KEY NAME={$name} MESSAGE={$message} */ I2P_ERROR, /** * Stream session ID doesn't exist * * -> STREAM CONNECT ... * <- STREAM STATUS RESULT=INVALID_ID MESSAGE={$message} * * -> STREAM ACCEPT ... * <- STREAM STATUS RESULT=INVALID_ID MESSAGE={$message} * * -> STREAM FORWARD ... * <- STREAM STATUS RESULT=INVALID_ID MESSAGE={$message} */ INVALID_ID, /** * The destination is not a valid private destination key * * -> SESSION CREATE ... * <- SESSION STATUS RESULT=INVALID_KEY MESSAGE={$message} * * -> STREAM CONNECT ... * <- STREAM STATUS RESULT=INVALID_KEY MESSAGE={$message} * * -> NAMING LOOKUP ... * <- NAMING REPLY RESULT=INVALID_KEY NAME={$name} MESSAGE={$message} */ INVALID_KEY, /** * The peer exists, but cannot be reached * * -> STREAM CONNECT ... * <- STREAM STATUS RESULT=CANT_REACH_PEER MESSAGE={$message} */ CANT_REACH_PEER, /** * Timeout while waiting for an event (e.g. peer answer) * * -> STREAM CONNECT ... * <- STREAM STATUS RESULT=TIMEOUT MESSAGE={$message} */ TIMEOUT, /** * The SAM bridge cannot find a suitable version * * -> HELLO VERSION ... * <- HELLO REPLY RESULT=NOVERSION MESSAGE={$message} */ NOVERSION, /** * The naming system can't resolve the given name * * -> NAMING LOOKUP ... * <- NAMING REPLY RESULT=INVALID_KEY NAME={$name} MESSAGE={$message} */ KEY_NOT_FOUND, /** * The peer cannot be found on the network * * ?? */ PEER_NOT_FOUND, /** * ?? * * -> STREAM ACCEPT * <- STREAM STATUS RESULT=ALREADY_ACCEPTING */ ALREADY_ACCEPTING, /** * ?? */ FAILED, /** * ?? */ CLOSED }; template struct Answer { const Message::eStatus status; T value; Answer(Message::eStatus status, const T &value) : status(status), value(value) {} explicit Answer(Message::eStatus status) : status(status), value() {} }; static std::string hello(const std::string &minVer, const std::string &maxVer); // Stream session static std::string sessionCreate(SessionStyle style, const std::string &sessionID, const std::string &nickname, const std::string &destination = SAM_GENERATE_MY_DESTINATION, const std::string &options = "", const std::string &signatureType = SAM_SIGNATURE_TYPE); static std::string streamAccept(const std::string &sessionID, bool silent = false); static std::string streamConnect(const std::string &sessionID, const std::string &destination, bool silent = false); static std::string streamForward(const std::string &sessionID, const std::string &host, uint16_t port, bool silent = false); // Datagram session static std::string sessionCreate(SessionStyle style, const std::string &sessionID, const std::string &nickname, const uint16_t port, const std::string &host = "127.0.0.1", const std::string &destination = SAM_GENERATE_MY_DESTINATION, const std::string &options = "", const std::string &signatureType = SAM_SIGNATURE_TYPE); static std::string datagramSend(const std::string &nickname, const std::string &destination/*, const std::string &datagram_payload*/); static std::string datagramParse(const std::string &datagram_payload); static std::string namingLookup(const std::string &name); static std::string destGenerate(); static eStatus checkAnswer(const std::string &answer); static std::string getValue(const std::string &answer, const std::string &key); private: template static std::string createSAMRequest(const char *msg) { return {msg}; } template static std::string createSAMRequest(const char *format, t_args &&... args) { // ToDo: Check allocated buffer size const int bufferStatus = std::snprintf(nullptr, 0, format, args...); if (bufferStatus < 0) { print_error("Failed to allocate buffer"); return {}; } std::vector buffer(bufferStatus + 1); const int status = std::snprintf(buffer.data(), buffer.size(), format, args...); if (status < 0) { print_error("Failed to format message"); return {}; } #ifdef DEBUG_ON_STDOUT std::cout << "Status: " << status << std::endl; #endif // DEBUG_ON_STDOUT return {buffer.data()}; } }; class I2pSocket { public: I2pSocket(const std::string &SAMHost, uint16_t SAMPort); I2pSocket(const sockaddr_in &addr); // explicit because we don't want to create any socket implicity explicit I2pSocket(const I2pSocket &rhs); // creates a new socket with the same parameters ~I2pSocket(); void bootstrapI2P(); void write(const std::string &msg); std::string read(); SOCKET release(); void close(); bool isOk() const; const std::string &getVersion() const; const std::string &getHost() const; uint16_t getPort() const; const sockaddr_in &getAddress() const; const std::string minVer_ = "3.0"; const std::string maxVer_ = "3.1"; private: SOCKET socket_; sockaddr_in servAddr_; std::string SAMHost_; uint16_t SAMPort_; std::string version_; #ifdef WIN32 static int instances_; static void initWSA(); static void freeWSA(); #endif // WIN32 void handshake(); void init(); I2pSocket &operator=(const I2pSocket &); }; struct FullDestination { std::string pub; std::string priv; bool isGenerated; FullDestination() {} FullDestination(const std::string &pub, const std::string &priv, bool isGenerated) : pub(pub), priv(priv), isGenerated(isGenerated) {} }; template struct RequestResult { bool isOk; T value; RequestResult() : isOk(false) {} explicit RequestResult(const T &value) : isOk(true), value(value) {} }; template struct RequestResult> { /** * a class-helper for resolving a problem with conversion * from temporary RequestResult to non-const RequestResult& */ struct RequestResultRef { bool isOk; T *value; RequestResultRef(bool isOk, T *value) : isOk(isOk), value(value) {} }; bool isOk; std::unique_ptr value; RequestResult() : isOk(false) {} explicit RequestResult(std::unique_ptr &&value) : isOk(true), value(std::move(value)) {} // some C++ magic RequestResult(RequestResultRef ref) : isOk(ref.isOk), value(ref.value) {} RequestResult &operator=(RequestResultRef ref) { if (value.get() != ref.value) { isOk = ref.isOk; value.reset(ref.value); } return *this; } operator RequestResultRef() { return RequestResultRef(this->isOk, this->value.release()); } }; template<> struct RequestResult { bool isOk; RequestResult() : isOk(false) {} explicit RequestResult(bool isOk) : isOk(isOk) {} }; class SAMSession { public: SAMSession(const std::string &nickname, const std::string &SAMHost = SAM_DEFAULT_ADDRESS, uint16_t SAMPort = SAM_DEFAULT_PORT_TCP, const std::string &i2pOptions = SAM_DEFAULT_I2P_OPTIONS, const std::string &signatureType = SAM_SIGNATURE_TYPE); SAMSession(SAMSession& rhs); virtual ~SAMSession() = default; static std::string generateSessionID(); RequestResult namingLookup(const std::string &name) const; RequestResult destGenerate() const; FullDestination createSession(const std::string &destination); FullDestination createSession(const std::string &destination, const std::string &sigType); virtual FullDestination createSession(const std::string &destination, const std::string &sigType, const std::string &i2pOptions) = 0; const FullDestination &getMyDestination() const; const sockaddr_in &getSAMAddress() const; const std::string &getSAMHost() const; uint16_t getSAMPort() const; const std::string &getNickname() const; const std::string &getSessionID() const; const std::string &getSAMMinVer() const; const std::string &getSAMMaxVer() const; const std::string &getSAMVersion() const; const std::string &getOptions() const; bool isSick() const; protected: static Message::Answer rawRequest(I2pSocket &socket, const std::string &requestStr); static Message::Answer request(I2pSocket &socket, const std::string &requestStr, const std::string &keyOnSuccess); static Message::eStatus request(I2pSocket &socket, const std::string &requestStr); static Message::Answer namingLookup(I2pSocket &socket, const std::string &name); static Message::Answer destGenerate(I2pSocket &socket); void fallSick() const; I2pSocket socket_; const std::string nickname_; const std::string sessionID_; FullDestination myDestination_; const std::string i2pOptions_; mutable bool isSick_; }; class StreamSession : public SAMSession { public: StreamSession(const std::string &nickname, const std::string &SAMHost = SAM_DEFAULT_ADDRESS, uint16_t SAMPort = SAM_DEFAULT_PORT_TCP, const std::string &destination = SAM_GENERATE_MY_DESTINATION, const std::string &i2pOptions = SAM_DEFAULT_I2P_OPTIONS, const std::string &signatureType = SAM_SIGNATURE_TYPE); explicit StreamSession(StreamSession &rhs); ~StreamSession(); RequestResult> accept(bool silent); RequestResult> connect(const std::string &destination, bool silent); RequestResult forward(const std::string &host, uint16_t port, bool silent); void stopForwarding(const std::string &host, uint16_t port); void stopForwardingAll(); private: StreamSession(const StreamSession &rhs); StreamSession &operator=(const StreamSession &rhs); struct ForwardedStream { I2pSocket *socket; std::string host; uint16_t port; bool silent; ForwardedStream(I2pSocket *socket, const std::string &host, uint16_t port, bool silent) : socket(socket), host(host), port(port), silent(silent) {} }; typedef std::list ForwardedStreamsContainer; ForwardedStreamsContainer forwardedStreams_; FullDestination createStreamSession(const std::string &destination); FullDestination createStreamSession(const std::string &destination, const std::string &sigType); FullDestination createStreamSession(const std::string &destination, const std::string &sigType, const std::string &i2pOptions); FullDestination createSession(const std::string &destination, const std::string &sigType, const std::string &i2pOptions); // commands static Message::Answer createStreamSession( I2pSocket &socket, const std::string &sessionID, const std::string &nickname, const std::string &destination, const std::string &options, const std::string &signatureType); static Message::eStatus accept(I2pSocket &socket, const std::string &sessionID, bool silent); static Message::eStatus connect(I2pSocket &socket, const std::string &sessionID, const std::string &destination, bool silent); static Message::eStatus forward(I2pSocket &socket, const std::string &sessionID, const std::string &host, uint16_t port, bool silent); }; /** * WARNING: * Can only create a TCP session for manage connection. * The UDP client/server is need to be implemented from your app side. */ class DatagramSession : public SAMSession { public: DatagramSession(const std::string &nickname, const std::string &SAMHost = SAM_DEFAULT_ADDRESS, uint16_t SAMPortTCP = SAM_DEFAULT_PORT_TCP, uint16_t SAMPortUDP = SAM_DEFAULT_PORT_UDP, const std::string &ClientAddress = SAM_DEFAULT_ADDRESS, uint16_t clientPortUDP = SAM_DEFAULT_CLIENT_UDP, const std::string &destination = SAM_GENERATE_MY_DESTINATION, const std::string &i2pOptions = SAM_DEFAULT_I2P_OPTIONS, const std::string &signatureType = SAM_SIGNATURE_TYPE); explicit DatagramSession(DatagramSession &rhs); ~DatagramSession(); private: DatagramSession(const DatagramSession &rhs); DatagramSession &operator=(const DatagramSession &rhs); std::string listenAddress_; uint16_t listenPortUDP_; uint16_t SAMPortUDP_; // commands FullDestination createDatagramSession(const std::string &destination); FullDestination createDatagramSession(const std::string &destination, const std::string &sigType); FullDestination createDatagramSession(const std::string &destination, const std::string &sigType, const std::string &i2pOptions); FullDestination createSession(const std::string &destination, const std::string &sigType, const std::string &i2pOptions); static Message::Answer createDatagramSession(I2pSocket &socket, const std::string &sessionID, const std::string &nickname, const uint16_t port, const std::string &host, const std::string &destination, const std::string &options, const std::string &signatureType); }; /** * WARNING: * Can only create a TCP session for manage connection. * The UDP client/server is need to be implemented from your app side. */ class RawSession : public SAMSession { public: RawSession(const std::string &nickname, const std::string &SAMHost = SAM_DEFAULT_ADDRESS, uint16_t SAMPortTCP = SAM_DEFAULT_PORT_TCP, uint16_t SAMPortUDP = SAM_DEFAULT_PORT_UDP, const std::string &ClientAddress = SAM_DEFAULT_ADDRESS, uint16_t clientPortUDP = SAM_DEFAULT_CLIENT_UDP, const std::string &destination = SAM_GENERATE_MY_DESTINATION, const std::string &i2pOptions = SAM_DEFAULT_I2P_OPTIONS, const std::string &signatureType = SAM_SIGNATURE_TYPE); explicit RawSession(RawSession &rhs); ~RawSession(); private: RawSession(const RawSession &rhs); RawSession &operator=(const RawSession &rhs); std::string listenAddress_; uint16_t listenPortUDP_; uint16_t SAMPortUDP_; FullDestination createRawSession(const std::string &destination); FullDestination createRawSession(const std::string &destination, const std::string &sigType); FullDestination createRawSession(const std::string &destination, const std::string &sigType, const std::string &i2pOptions); FullDestination createSession(const std::string &destination, const std::string &sigType, const std::string &i2pOptions); static Message::Answer createRawSession(I2pSocket &socket, const std::string &sessionID, const std::string &nickname, const uint16_t port, const std::string &host, const std::string &destination, const std::string &options, const std::string &signatureType); }; } // namespace SAM #else // __cplusplus #include "i2psam-c.h" #endif // __cplusplus #endif // I2PSAM_H__