diff --git a/AddressBook.cpp b/AddressBook.cpp index 766d8c95..5a07b3c8 100644 --- a/AddressBook.cpp +++ b/AddressBook.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include "Base.h" #include "util.h" @@ -716,12 +715,14 @@ namespace client if (status == 200) // OK { bool isChunked = false, isGzip = false; + m_Etag = ""; m_LastModified = ""; std::string header, statusMessage; std::getline (response, statusMessage); // read until new line meaning end of header while (!response.eof () && header != "\r") { std::getline (response, header); + if (response.fail ()) break; auto colon = header.find (':'); if (colon != std::string::npos) { @@ -741,7 +742,7 @@ namespace client } } LogPrint (eLogInfo, "Addressbook: received ", m_Link, " ETag: ", m_Etag, " Last-Modified: ", m_LastModified); - if (!response.eof ()) + if (!response.eof () && !response.fail ()) { success = true; if (!isChunked) diff --git a/ClientContext.h b/ClientContext.h index cfbc039f..f5696902 100644 --- a/ClientContext.h +++ b/ClientContext.h @@ -6,6 +6,7 @@ #include #include #include "Destination.h" +#include "I2PService.h" #include "HTTPProxy.h" #include "SOCKS.h" #include "I2PTunnel.h" diff --git a/Destination.cpp b/Destination.cpp index f487b9ad..40c8768e 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -13,17 +13,11 @@ namespace i2p { namespace client { - ClientDestination::ClientDestination (const i2p::data::PrivateKeys& keys, bool isPublic, - const std::map * params): - m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service), - m_Keys (keys), m_IsPublic (isPublic), m_PublishReplyToken (0), - m_DatagramDestination (nullptr), m_PublishConfirmationTimer (m_Service), + LeaseSetDestination::LeaseSetDestination (bool isPublic, const std::map * params): + m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service), m_IsPublic (isPublic), + m_PublishReplyToken (0), m_PublishConfirmationTimer (m_Service), m_PublishVerificationTimer (m_Service), m_CleanupTimer (m_Service) { - if (m_IsPublic) - PersistTemporaryKeys (); - else - i2p::crypto::GenerateElGamalKeyPair(m_EncryptionPrivateKey, m_EncryptionPublicKey); int inboundTunnelLen = DEFAULT_INBOUND_TUNNEL_LENGTH; int outboundTunnelLen = DEFAULT_OUTBOUND_TUNNEL_LENGTH; int inboundTunnelsQuantity = DEFAULT_INBOUND_TUNNELS_QUANTITY; @@ -103,11 +97,9 @@ namespace client m_Pool = i2p::tunnel::tunnels.CreateTunnelPool (inboundTunnelLen, outboundTunnelLen, inboundTunnelsQuantity, outboundTunnelsQuantity); if (explicitPeers) m_Pool->SetExplicitPeers (explicitPeers); - if (m_IsPublic) - LogPrint (eLogInfo, "Destination: Local address ", GetIdentHash().ToBase32 (), " created"); } - ClientDestination::~ClientDestination () + LeaseSetDestination::~LeaseSetDestination () { if (m_IsRunning) Stop (); @@ -116,11 +108,9 @@ namespace client m_LeaseSetRequests.clear (); if (m_Pool) i2p::tunnel::tunnels.DeleteTunnelPool (m_Pool); - if (m_DatagramDestination) - delete m_DatagramDestination; } - void ClientDestination::Run () + void LeaseSetDestination::Run () { while (m_IsRunning) { @@ -135,26 +125,29 @@ namespace client } } - void ClientDestination::Start () + bool LeaseSetDestination::Start () { if (!m_IsRunning) { m_IsRunning = true; + if (m_IsPublic) + PersistTemporaryKeys (); + else + i2p::crypto::GenerateElGamalKeyPair(m_EncryptionPrivateKey, m_EncryptionPublicKey); m_Pool->SetLocalDestination (shared_from_this ()); m_Pool->SetActive (true); - m_Thread = new std::thread (std::bind (&ClientDestination::Run, shared_from_this ())); - m_StreamingDestination = std::make_shared (shared_from_this ()); // TODO: - m_StreamingDestination->Start (); - for (auto it: m_StreamingDestinationsByPorts) - it.second->Start (); + m_Thread = new std::thread (std::bind (&LeaseSetDestination::Run, shared_from_this ())); m_CleanupTimer.expires_from_now (boost::posix_time::minutes (DESTINATION_CLEANUP_TIMEOUT)); - m_CleanupTimer.async_wait (std::bind (&ClientDestination::HandleCleanupTimer, + m_CleanupTimer.async_wait (std::bind (&LeaseSetDestination::HandleCleanupTimer, shared_from_this (), std::placeholders::_1)); + return true; } + else + return false; } - void ClientDestination::Stop () + bool LeaseSetDestination::Stop () { if (m_IsRunning) { @@ -162,16 +155,6 @@ namespace client m_PublishConfirmationTimer.cancel (); m_PublishVerificationTimer.cancel (); m_IsRunning = false; - m_StreamingDestination->Stop (); - m_StreamingDestination = nullptr; - for (auto it: m_StreamingDestinationsByPorts) - it.second->Stop (); - if (m_DatagramDestination) - { - auto d = m_DatagramDestination; - m_DatagramDestination = nullptr; - delete d; - } if (m_Pool) { m_Pool->SetLocalDestination (nullptr); @@ -184,10 +167,13 @@ namespace client delete m_Thread; m_Thread = 0; } + return true; } + else + return false; } - std::shared_ptr ClientDestination::FindLeaseSet (const i2p::data::IdentHash& ident) + std::shared_ptr LeaseSetDestination::FindLeaseSet (const i2p::data::IdentHash& ident) { auto it = m_RemoteLeaseSets.find (ident); if (it != m_RemoteLeaseSets.end ()) @@ -210,7 +196,7 @@ namespace client return nullptr; } - std::shared_ptr ClientDestination::GetLeaseSet () + std::shared_ptr LeaseSetDestination::GetLeaseSet () { if (!m_Pool) return nullptr; if (!m_LeaseSet) @@ -218,12 +204,17 @@ namespace client return m_LeaseSet; } - void ClientDestination::UpdateLeaseSet () + void LeaseSetDestination::UpdateLeaseSet () { - m_LeaseSet.reset (new i2p::data::LeaseSet (m_Pool)); + int numTunnels = m_Pool->GetNumInboundTunnels () + 2; // 2 backup tunnels + if (numTunnels > i2p::data::MAX_NUM_LEASES) numTunnels = i2p::data::MAX_NUM_LEASES; // 16 tunnels maximum + auto leaseSet = new i2p::data::LocalLeaseSet (GetIdentity (), GetEncryptionPublicKey (), + m_Pool->GetInboundTunnels (numTunnels)); + Sign (leaseSet->GetBuffer (), leaseSet->GetBufferLen () - leaseSet->GetSignatureLen (), leaseSet->GetSignature ()); // TODO + m_LeaseSet.reset (leaseSet); } - bool ClientDestination::SubmitSessionKey (const uint8_t * key, const uint8_t * tag) + bool LeaseSetDestination::SubmitSessionKey (const uint8_t * key, const uint8_t * tag) { struct { @@ -239,17 +230,17 @@ namespace client return true; } - void ClientDestination::ProcessGarlicMessage (std::shared_ptr msg) + void LeaseSetDestination::ProcessGarlicMessage (std::shared_ptr msg) { - m_Service.post (std::bind (&ClientDestination::HandleGarlicMessage, shared_from_this (), msg)); + m_Service.post (std::bind (&LeaseSetDestination::HandleGarlicMessage, shared_from_this (), msg)); } - void ClientDestination::ProcessDeliveryStatusMessage (std::shared_ptr msg) + void LeaseSetDestination::ProcessDeliveryStatusMessage (std::shared_ptr msg) { - m_Service.post (std::bind (&ClientDestination::HandleDeliveryStatusMessage, shared_from_this (), msg)); + m_Service.post (std::bind (&LeaseSetDestination::HandleDeliveryStatusMessage, shared_from_this (), msg)); } - void ClientDestination::HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from) + void LeaseSetDestination::HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from) { uint8_t typeID = buf[I2NP_HEADER_TYPEID_OFFSET]; switch (typeID) @@ -272,7 +263,7 @@ namespace client } } - void ClientDestination::HandleDatabaseStoreMessage (const uint8_t * buf, size_t len) + void LeaseSetDestination::HandleDatabaseStoreMessage (const uint8_t * buf, size_t len) { uint32_t replyToken = bufbe32toh (buf + DATABASE_STORE_REPLY_TOKEN_OFFSET); size_t offset = DATABASE_STORE_HEADER_SIZE; @@ -336,7 +327,7 @@ namespace client } } - void ClientDestination::HandleDatabaseSearchReplyMessage (const uint8_t * buf, size_t len) + void LeaseSetDestination::HandleDatabaseSearchReplyMessage (const uint8_t * buf, size_t len) { i2p::data::IdentHash key (buf); int num = buf[32]; // num @@ -379,7 +370,7 @@ namespace client LogPrint (eLogWarning, "Destination: Request for ", key.ToBase64 (), " not found"); } - void ClientDestination::HandleDeliveryStatusMessage (std::shared_ptr msg) + void LeaseSetDestination::HandleDeliveryStatusMessage (std::shared_ptr msg) { uint32_t msgID = bufbe32toh (msg->GetPayload () + DELIVERY_STATUS_MSGID_OFFSET); if (msgID == m_PublishReplyToken) @@ -389,14 +380,14 @@ namespace client m_PublishReplyToken = 0; // schedule verification m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_VERIFICATION_TIMEOUT)); - m_PublishVerificationTimer.async_wait (std::bind (&ClientDestination::HandlePublishVerificationTimer, + m_PublishVerificationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer, shared_from_this (), std::placeholders::_1)); } else i2p::garlic::GarlicDestination::HandleDeliveryStatusMessage (msg); } - void ClientDestination::SetLeaseSetUpdated () + void LeaseSetDestination::SetLeaseSetUpdated () { i2p::garlic::GarlicDestination::SetLeaseSetUpdated (); UpdateLeaseSet (); @@ -407,7 +398,7 @@ namespace client } } - void ClientDestination::Publish () + void LeaseSetDestination::Publish () { if (!m_LeaseSet || !m_Pool) { @@ -425,6 +416,12 @@ namespace client LogPrint (eLogError, "Destination: Can't publish LeaseSet. No outbound tunnels"); return; } + auto inbound = m_Pool->GetNextInboundTunnel (); + if (!inbound) + { + LogPrint (eLogError, "Destination: Can't publish LeaseSet. No inbound tunnels"); + return; + } auto floodfill = i2p::data::netdb.GetClosestFloodfill (m_LeaseSet->GetIdentHash (), m_ExcludedFloodfills); if (!floodfill) { @@ -435,14 +432,14 @@ namespace client m_ExcludedFloodfills.insert (floodfill->GetIdentHash ()); LogPrint (eLogDebug, "Destination: Publish LeaseSet of ", GetIdentHash ().ToBase32 ()); RAND_bytes ((uint8_t *)&m_PublishReplyToken, 4); - auto msg = WrapMessage (floodfill, i2p::CreateDatabaseStoreMsg (m_LeaseSet, m_PublishReplyToken)); + auto msg = WrapMessage (floodfill, i2p::CreateDatabaseStoreMsg (m_LeaseSet, m_PublishReplyToken, inbound)); m_PublishConfirmationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_CONFIRMATION_TIMEOUT)); - m_PublishConfirmationTimer.async_wait (std::bind (&ClientDestination::HandlePublishConfirmationTimer, + m_PublishConfirmationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishConfirmationTimer, shared_from_this (), std::placeholders::_1)); outbound->SendTunnelDataMsg (floodfill->GetIdentHash (), 0, msg); } - void ClientDestination::HandlePublishConfirmationTimer (const boost::system::error_code& ecode) + void LeaseSetDestination::HandlePublishConfirmationTimer (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { @@ -455,7 +452,7 @@ namespace client } } - void ClientDestination::HandlePublishVerificationTimer (const boost::system::error_code& ecode) + void LeaseSetDestination::HandlePublishVerificationTimer (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { @@ -471,7 +468,7 @@ namespace client // we got latest LeasetSet LogPrint (eLogDebug, "Destination: published LeaseSet verified"); s->m_PublishVerificationTimer.expires_from_now (boost::posix_time::seconds(PUBLISH_REGULAR_VERIFICATION_INTERNAL)); - s->m_PublishVerificationTimer.async_wait (std::bind (&ClientDestination::HandlePublishVerificationTimer, s, std::placeholders::_1)); + s->m_PublishVerificationTimer.async_wait (std::bind (&LeaseSetDestination::HandlePublishVerificationTimer, s, std::placeholders::_1)); return; } } @@ -483,129 +480,18 @@ namespace client } } - void ClientDestination::HandleDataMessage (const uint8_t * buf, size_t len) - { - uint32_t length = bufbe32toh (buf); - buf += 4; - // we assume I2CP payload - uint16_t fromPort = bufbe16toh (buf + 4), // source - toPort = bufbe16toh (buf + 6); // destination - switch (buf[9]) - { - case PROTOCOL_TYPE_STREAMING: - { - // streaming protocol - auto dest = GetStreamingDestination (toPort); - if (dest) - dest->HandleDataMessagePayload (buf, length); - else - LogPrint (eLogError, "Destination: Missing streaming destination"); - } - break; - case PROTOCOL_TYPE_DATAGRAM: - // datagram protocol - if (m_DatagramDestination) - m_DatagramDestination->HandleDataMessagePayload (fromPort, toPort, buf, length); - else - LogPrint (eLogError, "Destination: Missing datagram destination"); - break; - default: - LogPrint (eLogError, "Destination: Data: unexpected protocol ", buf[9]); - } - } - - void ClientDestination::CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, int port) - { - if (!streamRequestComplete) - { - LogPrint (eLogError, "Destination: request callback is not specified in CreateStream"); - return; - } - auto leaseSet = FindLeaseSet (dest); - if (leaseSet) - streamRequestComplete(CreateStream (leaseSet, port)); - else - { - auto s = shared_from_this (); - RequestDestination (dest, - [s, streamRequestComplete, port](std::shared_ptr ls) - { - if (ls) - streamRequestComplete(s->CreateStream (ls, port)); - else - streamRequestComplete (nullptr); - }); - } - } - - std::shared_ptr ClientDestination::CreateStream (std::shared_ptr remote, int port) - { - if (m_StreamingDestination) - return m_StreamingDestination->CreateNewOutgoingStream (remote, port); - else - return nullptr; - } - - std::shared_ptr ClientDestination::GetStreamingDestination (int port) const - { - if (port) - { - auto it = m_StreamingDestinationsByPorts.find (port); - if (it != m_StreamingDestinationsByPorts.end ()) - return it->second; - } - // if port is zero or not found, use default destination - return m_StreamingDestination; - } - - void ClientDestination::AcceptStreams (const i2p::stream::StreamingDestination::Acceptor& acceptor) - { - if (m_StreamingDestination) - m_StreamingDestination->SetAcceptor (acceptor); - } - - void ClientDestination::StopAcceptingStreams () - { - if (m_StreamingDestination) - m_StreamingDestination->ResetAcceptor (); - } - - bool ClientDestination::IsAcceptingStreams () const - { - if (m_StreamingDestination) - return m_StreamingDestination->IsAcceptorSet (); - return false; - } - - std::shared_ptr ClientDestination::CreateStreamingDestination (int port, bool gzip) - { - auto dest = std::make_shared (shared_from_this (), port, gzip); - if (port) - m_StreamingDestinationsByPorts[port] = dest; - else // update default - m_StreamingDestination = dest; - return dest; - } - - i2p::datagram::DatagramDestination * ClientDestination::CreateDatagramDestination () - { - if (!m_DatagramDestination) - m_DatagramDestination = new i2p::datagram::DatagramDestination (shared_from_this ()); - return m_DatagramDestination; - } - - bool ClientDestination::RequestDestination (const i2p::data::IdentHash& dest, RequestComplete requestComplete) + bool LeaseSetDestination::RequestDestination (const i2p::data::IdentHash& dest, RequestComplete requestComplete) { if (!m_Pool || !IsReady ()) { if (requestComplete) requestComplete (nullptr); return false; } - m_Service.post (std::bind (&ClientDestination::RequestLeaseSet, shared_from_this (), dest, requestComplete)); + m_Service.post (std::bind (&LeaseSetDestination::RequestLeaseSet, shared_from_this (), dest, requestComplete)); return true; } - void ClientDestination::CancelDestinationRequest (const i2p::data::IdentHash& dest) + void LeaseSetDestination::CancelDestinationRequest (const i2p::data::IdentHash& dest) { auto s = shared_from_this (); m_Service.post ([dest, s](void) @@ -620,7 +506,7 @@ namespace client }); } - void ClientDestination::RequestLeaseSet (const i2p::data::IdentHash& dest, RequestComplete requestComplete) + void LeaseSetDestination::RequestLeaseSet (const i2p::data::IdentHash& dest, RequestComplete requestComplete) { std::set excluded; auto floodfill = i2p::data::netdb.GetClosestFloodfill (dest, excluded); @@ -652,7 +538,7 @@ namespace client } } - bool ClientDestination::SendLeaseSetRequest (const i2p::data::IdentHash& dest, + bool LeaseSetDestination::SendLeaseSetRequest (const i2p::data::IdentHash& dest, std::shared_ptr nextFloodfill, std::shared_ptr request) { if (!request->replyTunnel || !request->replyTunnel->IsEstablished ()) @@ -685,7 +571,7 @@ namespace client } }); request->requestTimeoutTimer.expires_from_now (boost::posix_time::seconds(LEASESET_REQUEST_TIMEOUT)); - request->requestTimeoutTimer.async_wait (std::bind (&ClientDestination::HandleRequestTimoutTimer, + request->requestTimeoutTimer.async_wait (std::bind (&LeaseSetDestination::HandleRequestTimoutTimer, shared_from_this (), std::placeholders::_1, dest)); } else @@ -693,7 +579,7 @@ namespace client return true; } - void ClientDestination::HandleRequestTimoutTimer (const boost::system::error_code& ecode, const i2p::data::IdentHash& dest) + void LeaseSetDestination::HandleRequestTimoutTimer (const boost::system::error_code& ecode, const i2p::data::IdentHash& dest) { if (ecode != boost::asio::error::operation_aborted) { @@ -731,19 +617,19 @@ namespace client } } - void ClientDestination::HandleCleanupTimer (const boost::system::error_code& ecode) + void LeaseSetDestination::HandleCleanupTimer (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { CleanupExpiredTags (); CleanupRemoteLeaseSets (); m_CleanupTimer.expires_from_now (boost::posix_time::minutes (DESTINATION_CLEANUP_TIMEOUT)); - m_CleanupTimer.async_wait (std::bind (&ClientDestination::HandleCleanupTimer, + m_CleanupTimer.async_wait (std::bind (&LeaseSetDestination::HandleCleanupTimer, shared_from_this (), std::placeholders::_1)); } } - void ClientDestination::CleanupRemoteLeaseSets () + void LeaseSetDestination::CleanupRemoteLeaseSets () { auto ts = i2p::util::GetMillisecondsSinceEpoch (); for (auto it = m_RemoteLeaseSets.begin (); it != m_RemoteLeaseSets.end ();) @@ -758,7 +644,7 @@ namespace client } } - void ClientDestination::PersistTemporaryKeys () + void LeaseSetDestination::PersistTemporaryKeys () { std::string ident = GetIdentHash().ToBase32(); std::string path = i2p::fs::DataDirPath("destinations", (ident + ".dat")); @@ -780,6 +666,165 @@ namespace client return; } LogPrint(eLogError, "Destinations: Can't save keys to ", path); + } + + ClientDestination::ClientDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params): + LeaseSetDestination (isPublic, params), + m_Keys (keys), m_DatagramDestination (nullptr) + { + if (isPublic) + LogPrint (eLogInfo, "Destination: Local address ", GetIdentHash().ToBase32 (), " created"); + } + + ClientDestination::~ClientDestination () + { + if (m_DatagramDestination) + delete m_DatagramDestination; + } + + bool ClientDestination::Start () + { + if (LeaseSetDestination::Start ()) + { + m_StreamingDestination = std::make_shared (GetSharedFromThis ()); // TODO: + m_StreamingDestination->Start (); + for (auto it: m_StreamingDestinationsByPorts) + it.second->Start (); + return true; + } + else + return false; + } + + bool ClientDestination::Stop () + { + if (LeaseSetDestination::Stop ()) + { + m_StreamingDestination->Stop (); + m_StreamingDestination = nullptr; + for (auto it: m_StreamingDestinationsByPorts) + it.second->Stop (); + if (m_DatagramDestination) + { + auto d = m_DatagramDestination; + m_DatagramDestination = nullptr; + delete d; + } + return true; + } + else + return false; + } + + void ClientDestination::HandleDataMessage (const uint8_t * buf, size_t len) + { + uint32_t length = bufbe32toh (buf); + buf += 4; + // we assume I2CP payload + uint16_t fromPort = bufbe16toh (buf + 4), // source + toPort = bufbe16toh (buf + 6); // destination + switch (buf[9]) + { + case PROTOCOL_TYPE_STREAMING: + { + // streaming protocol + auto dest = GetStreamingDestination (toPort); + if (dest) + dest->HandleDataMessagePayload (buf, length); + else + LogPrint (eLogError, "Destination: Missing streaming destination"); + } + break; + case PROTOCOL_TYPE_DATAGRAM: + // datagram protocol + if (m_DatagramDestination) + m_DatagramDestination->HandleDataMessagePayload (fromPort, toPort, buf, length); + else + LogPrint (eLogError, "Destination: Missing datagram destination"); + break; + default: + LogPrint (eLogError, "Destination: Data: unexpected protocol ", buf[9]); + } + } + + void ClientDestination::CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, int port) + { + if (!streamRequestComplete) + { + LogPrint (eLogError, "Destination: request callback is not specified in CreateStream"); + return; + } + auto leaseSet = FindLeaseSet (dest); + if (leaseSet) + streamRequestComplete(CreateStream (leaseSet, port)); + else + { + auto s = GetSharedFromThis (); + RequestDestination (dest, + [s, streamRequestComplete, port](std::shared_ptr ls) + { + if (ls) + streamRequestComplete(s->CreateStream (ls, port)); + else + streamRequestComplete (nullptr); + }); + } + } + + std::shared_ptr ClientDestination::CreateStream (std::shared_ptr remote, int port) + { + if (m_StreamingDestination) + return m_StreamingDestination->CreateNewOutgoingStream (remote, port); + else + return nullptr; + } + + std::shared_ptr ClientDestination::GetStreamingDestination (int port) const + { + if (port) + { + auto it = m_StreamingDestinationsByPorts.find (port); + if (it != m_StreamingDestinationsByPorts.end ()) + return it->second; + } + // if port is zero or not found, use default destination + return m_StreamingDestination; + } + + void ClientDestination::AcceptStreams (const i2p::stream::StreamingDestination::Acceptor& acceptor) + { + if (m_StreamingDestination) + m_StreamingDestination->SetAcceptor (acceptor); + } + + void ClientDestination::StopAcceptingStreams () + { + if (m_StreamingDestination) + m_StreamingDestination->ResetAcceptor (); + } + + bool ClientDestination::IsAcceptingStreams () const + { + if (m_StreamingDestination) + return m_StreamingDestination->IsAcceptorSet (); + return false; + } + + std::shared_ptr ClientDestination::CreateStreamingDestination (int port, bool gzip) + { + auto dest = std::make_shared (GetSharedFromThis (), port, gzip); + if (port) + m_StreamingDestinationsByPorts[port] = dest; + else // update default + m_StreamingDestination = dest; + return dest; + } + + i2p::datagram::DatagramDestination * ClientDestination::CreateDatagramDestination () + { + if (!m_DatagramDestination) + m_DatagramDestination = new i2p::datagram::DatagramDestination (GetSharedFromThis ()); + return m_DatagramDestination; } std::vector > ClientDestination::GetAllStreams () const diff --git a/Destination.h b/Destination.h index 3011b4cb..e2531699 100644 --- a/Destination.h +++ b/Destination.h @@ -49,8 +49,8 @@ namespace client typedef std::function stream)> StreamRequestComplete; - class ClientDestination: public i2p::garlic::GarlicDestination, - public std::enable_shared_from_this + class LeaseSetDestination: public i2p::garlic::GarlicDestination, + public std::enable_shared_from_this { typedef std::function leaseSet)> RequestComplete; // leaseSet = nullptr means not found @@ -68,11 +68,11 @@ namespace client public: - ClientDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params = nullptr); - ~ClientDestination (); + LeaseSetDestination (bool isPublic, const std::map * params = nullptr); + ~LeaseSetDestination (); - virtual void Start (); - virtual void Stop (); + virtual bool Start (); + virtual bool Stop (); bool IsRunning () const { return m_IsRunning; }; boost::asio::io_service& GetService () { return m_Service; }; std::shared_ptr GetTunnelPool () { return m_Pool; }; @@ -80,28 +80,13 @@ namespace client std::shared_ptr FindLeaseSet (const i2p::data::IdentHash& ident); bool RequestDestination (const i2p::data::IdentHash& dest, RequestComplete requestComplete = nullptr); void CancelDestinationRequest (const i2p::data::IdentHash& dest); - - // streaming - std::shared_ptr CreateStreamingDestination (int port, bool gzip = true); // additional - std::shared_ptr GetStreamingDestination (int port = 0) const; - // following methods operate with default streaming destination - void CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, int port = 0); - std::shared_ptr CreateStream (std::shared_ptr remote, int port = 0); - void AcceptStreams (const i2p::stream::StreamingDestination::Acceptor& acceptor); - void StopAcceptingStreams (); - bool IsAcceptingStreams () const; - - // datagram - i2p::datagram::DatagramDestination * GetDatagramDestination () const { return m_DatagramDestination; }; - i2p::datagram::DatagramDestination * CreateDatagramDestination (); - // implements LocalDestination - const i2p::data::PrivateKeys& GetPrivateKeys () const { return m_Keys; }; + // implements LocalDestination const uint8_t * GetEncryptionPrivateKey () const { return m_EncryptionPrivateKey; }; const uint8_t * GetEncryptionPublicKey () const { return m_EncryptionPublicKey; }; - + // implements GarlicDestination - std::shared_ptr GetLeaseSet (); + std::shared_ptr GetLeaseSet (); std::shared_ptr GetTunnelPool () const { return m_Pool; } void HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from); @@ -111,9 +96,11 @@ namespace client void ProcessDeliveryStatusMessage (std::shared_ptr msg); void SetLeaseSetUpdated (); - // I2CP - void HandleDataMessage (const uint8_t * buf, size_t len); + protected: + // I2CP + virtual void HandleDataMessage (const uint8_t * buf, size_t len) = 0; + private: void Run (); @@ -129,29 +116,26 @@ namespace client bool SendLeaseSetRequest (const i2p::data::IdentHash& dest, std::shared_ptr nextFloodfill, std::shared_ptr request); void HandleRequestTimoutTimer (const boost::system::error_code& ecode, const i2p::data::IdentHash& dest); void HandleCleanupTimer (const boost::system::error_code& ecode); - void CleanupRemoteLeaseSets (); - void PersistTemporaryKeys (); + void CleanupRemoteLeaseSets (); + void PersistTemporaryKeys (); + private: + uint8_t m_EncryptionPublicKey[256], m_EncryptionPrivateKey[256]; + volatile bool m_IsRunning; std::thread * m_Thread; boost::asio::io_service m_Service; boost::asio::io_service::work m_Work; - i2p::data::PrivateKeys m_Keys; - uint8_t m_EncryptionPublicKey[256], m_EncryptionPrivateKey[256]; std::map > m_RemoteLeaseSets; std::map > m_LeaseSetRequests; std::shared_ptr m_Pool; - std::shared_ptr m_LeaseSet; + std::shared_ptr m_LeaseSet; bool m_IsPublic; uint32_t m_PublishReplyToken; std::set m_ExcludedFloodfills; // for publishing - - std::shared_ptr m_StreamingDestination; // default - std::map > m_StreamingDestinationsByPorts; - i2p::datagram::DatagramDestination * m_DatagramDestination; boost::asio::deadline_timer m_PublishConfirmationTimer, m_PublishVerificationTimer, m_CleanupTimer; @@ -159,6 +143,59 @@ namespace client // for HTTP only int GetNumRemoteLeaseSets () const { return m_RemoteLeaseSets.size (); }; + }; + + class ClientDestination: public LeaseSetDestination + { + public: + + ClientDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params = nullptr); + ~ClientDestination (); + + bool Start (); + bool Stop (); + + const i2p::data::PrivateKeys& GetPrivateKeys () const { return m_Keys; }; + + // streaming + std::shared_ptr CreateStreamingDestination (int port, bool gzip = true); // additional + std::shared_ptr GetStreamingDestination (int port = 0) const; + // following methods operate with default streaming destination + void CreateStream (StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash& dest, int port = 0); + std::shared_ptr CreateStream (std::shared_ptr remote, int port = 0); + void AcceptStreams (const i2p::stream::StreamingDestination::Acceptor& acceptor); + void StopAcceptingStreams (); + bool IsAcceptingStreams () const; + + // datagram + i2p::datagram::DatagramDestination * GetDatagramDestination () const { return m_DatagramDestination; }; + i2p::datagram::DatagramDestination * CreateDatagramDestination (); + + // implements LocalDestination + std::shared_ptr GetIdentity () const { return m_Keys.GetPublic (); }; + void Sign (const uint8_t * buf, int len, uint8_t * signature) const { m_Keys.Sign (buf, len, signature); }; + + protected: + + // I2CP + void HandleDataMessage (const uint8_t * buf, size_t len); + + private: + + std::shared_ptr GetSharedFromThis () + { return std::static_pointer_cast(shared_from_this ()); } + + private: + + i2p::data::PrivateKeys m_Keys; + + std::shared_ptr m_StreamingDestination; // default + std::map > m_StreamingDestinationsByPorts; + i2p::datagram::DatagramDestination * m_DatagramDestination; + + public: + + // for HTTP only std::vector > GetAllStreams () const; }; } diff --git a/Family.cpp b/Family.cpp index 90c5ccd0..ff09f2f5 100644 --- a/Family.cpp +++ b/Family.cpp @@ -94,7 +94,7 @@ namespace data int numCertificates = 0; if (!i2p::fs::ReadDir(certDir, files)) { - LogPrint(eLogWarning, "Reseed: Can't load reseed certificates from ", certDir); + LogPrint(eLogWarning, "Family: Can't load family certificates from ", certDir); return; } diff --git a/Garlic.h b/Garlic.h index ea53dd03..6d25fd39 100644 --- a/Garlic.h +++ b/Garlic.h @@ -163,7 +163,7 @@ namespace garlic virtual void ProcessDeliveryStatusMessage (std::shared_ptr msg); virtual void SetLeaseSetUpdated (); - virtual std::shared_ptr GetLeaseSet () = 0; // TODO + virtual std::shared_ptr GetLeaseSet () = 0; // TODO virtual std::shared_ptr GetTunnelPool () const = 0; virtual void HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from) = 0; diff --git a/HTTP.cpp b/HTTP.cpp index ef43d55f..3f6fd937 100644 --- a/HTTP.cpp +++ b/HTTP.cpp @@ -8,6 +8,7 @@ #include "HTTP.h" #include +#include namespace i2p { namespace http { @@ -44,9 +45,10 @@ namespace http { bool parse_header_line(const std::string & line, std::map & headers) { std::size_t pos = 0; std::size_t len = 2; /* strlen(": ") */ + std::size_t max = line.length(); if ((pos = line.find(": ", pos)) == std::string::npos) return false; - while (isspace(line.at(pos + len))) + while ((pos + len) < max && isspace(line.at(pos + len))) len++; std::string name = line.substr(0, pos); std::string value = line.substr(pos + len); @@ -54,6 +56,13 @@ namespace http { return true; } + void gen_rfc1123_date(std::string & out) { + std::time_t now = std::time(nullptr); + char buf[128]; + std::strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S GMT", std::gmtime(&now)); + out = buf; + } + bool URL::parse(const char *str, std::size_t len) { std::string url(str, len ? len : strlen(str)); return parse(url); @@ -184,6 +193,25 @@ namespace http { return out; } + void HTTPMsg::add_header(const char *name, std::string & value, bool replace) { + add_header(name, value.c_str(), replace); + } + + void HTTPMsg::add_header(const char *name, const char *value, bool replace) { + std::size_t count = headers.count(name); + if (count && !replace) + return; + if (count) { + headers[name] = value; + return; + } + headers.insert(std::pair(name, value)); + } + + void HTTPMsg::del_header(const char *name) { + headers.erase(name); + } + int HTTPReq::parse(const char *buf, size_t len) { std::string str(buf, len); return parse(str); @@ -256,7 +284,7 @@ namespace http { return false; } - long int HTTPRes::length() { + long int HTTPMsg::length() { unsigned long int length = 0; auto it = headers.find("Content-Length"); if (it == headers.end()) @@ -311,12 +339,24 @@ namespace http { } std::string HTTPRes::to_string() { + if (version == "HTTP/1.1" && headers.count("Date") == 0) { + std::string date; + gen_rfc1123_date(date); + add_header("Date", date.c_str()); + } + if (status == "OK" && code != 200) + status = HTTPCodeToStatus(code); // update + if (body.length() > 0 && headers.count("Content-Length") == 0) + add_header("Content-Length", std::to_string(body.length()).c_str()); + /* build response */ std::stringstream ss; ss << version << " " << code << " " << status << CRLF; for (auto & h : headers) { ss << h.first << ": " << h.second << CRLF; } ss << CRLF; + if (body.length() > 0) + ss << body; return ss.str(); } diff --git a/HTTP.h b/HTTP.h index 9bd31c75..f227271f 100644 --- a/HTTP.h +++ b/HTTP.h @@ -54,8 +54,18 @@ namespace http { std::string to_string (); }; - struct HTTPReq { + struct HTTPMsg { std::map headers; + + void add_header(const char *name, std::string & value, bool replace = false); + void add_header(const char *name, const char *value, bool replace = false); + void del_header(const char *name); + + /** @brief Returns declared message length or -1 if unknown */ + long int length(); + }; + + struct HTTPReq : HTTPMsg { std::string version; std::string method; std::string uri; @@ -75,11 +85,16 @@ namespace http { std::string to_string(); }; - struct HTTPRes { - std::map headers; + struct HTTPRes : HTTPMsg { std::string version; std::string status; unsigned short int code; + /** simplifies response generation + * If this variable is set: + * a) Content-Length header will be added if missing + * b) contents of body will be included in response + */ + std::string body; HTTPRes (): version("HTTP/1.1"), status("OK"), code(200) {} @@ -91,14 +106,17 @@ namespace http { int parse(const char *buf, size_t len); int parse(const std::string& buf); - /** @brief Serialize HTTP response to string */ + /** + * @brief Serialize HTTP response to string + * @note If version is set to HTTP/1.1, and Date header is missing, + * it will be generated based on current time and added to headers + * @note If body member is set and Content-Length header is missing, + * this header will be added, based on body's length + */ std::string to_string(); /** @brief Checks that response declared as chunked data */ bool is_chunked(); - - /** @brief Returns declared response length or -1 if unknown */ - long int length(); }; /** @@ -115,6 +133,14 @@ namespace http { * @return Decoded string */ std::string UrlDecode(const std::string& data, bool null = false); + + /** + * @brief Merge HTTP response content with Transfer-Encoding: chunked + * @param in Input stream + * @param out Output stream + * @return true on success, false otherwise + */ + bool MergeChunkedResponse (std::istream& in, std::ostream& out); } // http } // i2p diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index 6104e15e..f681f365 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -4,6 +4,13 @@ #include #include #include +#include +#include +#include +#include + +#include "I2PService.h" +#include "Destination.h" #include "HTTPProxy.h" #include "util.h" #include "Identity.h" @@ -36,7 +43,7 @@ namespace proxy void HandleSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered); void Terminate(); void AsyncSockRead(); - void HTTPRequestFailed(/*std::string message*/); + void HTTPRequestFailed(const char *message); void RedirectToJumpService(); void ExtractRequest(); bool IsI2PAddress(); @@ -49,6 +56,7 @@ namespace proxy uint8_t m_http_buff[http_buffer_size]; std::shared_ptr m_sock; std::string m_request; //Data left to be sent + std::string m_Response; std::string m_url; //URL std::string m_method; //Method std::string m_version; //HTTP version @@ -91,10 +99,17 @@ namespace proxy /* All hope is lost beyond this point */ //TODO: handle this apropriately - void HTTPProxyHandler::HTTPRequestFailed(/*HTTPProxyHandler::errTypes error*/) + void HTTPProxyHandler::HTTPRequestFailed(const char *message) { - static std::string response = "HTTP/1.0 500 Internal Server Error\r\nContent-type: text/html\r\nContent-length: 0\r\n"; - boost::asio::async_write(*m_sock, boost::asio::buffer(response,response.size()), + std::size_t size = std::strlen(message); + std::stringstream ss; + ss << "HTTP/1.0 500 Internal Server Error\r\n" + << "Content-Type: text/plain\r\n"; + ss << "Content-Length: " << std::to_string(size + 2) << "\r\n" + << "\r\n"; /* end of headers */ + ss << message << "\r\n"; + m_Response = ss.str(); + boost::asio::async_write(*m_sock, boost::asio::buffer(m_Response), std::bind(&HTTPProxyHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); } @@ -105,7 +120,8 @@ namespace proxy uint16_t httpPort; i2p::config::GetOption("http.port", httpPort); response << "HTTP/1.1 302 Found\r\nLocation: http://" << httpAddr << ":" << httpPort << "/?page=jumpservices&address=" << m_address << "\r\n\r\n"; - boost::asio::async_write(*m_sock, boost::asio::buffer(response.str (),response.str ().length ()), + m_Response = response.str (); + boost::asio::async_write(*m_sock, boost::asio::buffer(m_Response), std::bind(&HTTPProxyHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); } @@ -139,7 +155,7 @@ namespace proxy if ( m_version != "HTTP/1.0" && m_version != "HTTP/1.1" ) { LogPrint(eLogError, "HTTPProxy: unsupported version: ", m_version); - HTTPRequestFailed(); //TODO: send right stuff + HTTPRequestFailed("unsupported HTTP version"); return false; } return true; @@ -276,13 +292,13 @@ namespace proxy case '\n': EnterState(DONE); break; default: LogPrint(eLogError, "HTTPProxy: rejected invalid request ending with: ", ((int)*http_buff)); - HTTPRequestFailed(); //TODO: add correct code + HTTPRequestFailed("rejected invalid request"); return false; } break; default: LogPrint(eLogError, "HTTPProxy: invalid state: ", m_state); - HTTPRequestFailed(); //TODO: add correct code 500 + HTTPRequestFailed("invalid parser state"); return false; } http_buff++; @@ -338,7 +354,7 @@ namespace proxy else { LogPrint (eLogError, "HTTPProxy: error when creating the stream, check the previous warnings for more info"); - HTTPRequestFailed(); // TODO: Send correct error message host unreachable + HTTPRequestFailed("error when creating the stream, check logs"); } } @@ -351,6 +367,5 @@ namespace proxy { return std::make_shared (this, socket); } - } } diff --git a/HTTPProxy.h b/HTTPProxy.h index b5ed77b9..0356adb5 100644 --- a/HTTPProxy.h +++ b/HTTPProxy.h @@ -1,13 +1,6 @@ #ifndef HTTP_PROXY_H__ #define HTTP_PROXY_H__ -#include -#include -#include -#include -#include "I2PService.h" -#include "Destination.h" - namespace i2p { namespace proxy diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 81515105..5d54711d 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -1,4 +1,3 @@ -#include #include #include #include @@ -241,15 +240,15 @@ namespace http { if ((num = seconds / 86400) > 0) { s << num << " days, "; - seconds -= num; + seconds -= num * 86400; } if ((num = seconds / 3600) > 0) { s << num << " hours, "; - seconds -= num; + seconds -= num * 3600; } if ((num = seconds / 60) > 0) { s << num << " min, "; - seconds -= num; + seconds -= num * 60; } s << seconds << " seconds"; } @@ -749,7 +748,7 @@ namespace http { if (needAuth && !CheckAuth(req)) { res.code = 401; - res.headers.insert(std::pair("WWW-Authenticate", "Basic realm=\"WebAdmin\"")); + res.add_header("WWW-Authenticate", "Basic realm=\"WebAdmin\""); SendReply(res, content); return; } @@ -763,6 +762,8 @@ namespace http { else ShowStatus (s); ShowPageTail (s); + + res.code = 200; content = s.str (); SendReply (res, content); } @@ -845,21 +846,11 @@ namespace http { void HTTPConnection::SendReply (HTTPRes& reply, std::string& content) { - std::time_t time_now = std::time(nullptr); - char time_buff[128]; - std::strftime(time_buff, sizeof(time_buff), "%a, %d %b %Y %H:%M:%S GMT", std::gmtime(&time_now)); - reply.status = HTTPCodeToStatus(reply.code); - reply.headers.insert(std::pair("Date", time_buff)); - reply.headers.insert(std::pair("Content-Type", "text/html")); - reply.headers.insert(std::pair("Content-Length", std::to_string(content.size()))); + reply.add_header("Content-Type", "text/html"); + reply.body = content; std::string res = reply.to_string(); - std::vector buffers; - - buffers.push_back(boost::asio::buffer(res)); - buffers.push_back(boost::asio::buffer(content)); - - boost::asio::async_write (*m_Socket, buffers, + boost::asio::async_write (*m_Socket, boost::asio::buffer(res), std::bind (&HTTPConnection::Terminate, shared_from_this (), std::placeholders::_1)); } diff --git a/I2CP.cpp b/I2CP.cpp index d6ba4e42..5c718a48 100644 --- a/I2CP.cpp +++ b/I2CP.cpp @@ -8,6 +8,12 @@ namespace i2p { namespace client { + + I2CPDestination::I2CPDestination (I2CPSession& owner, std::shared_ptr identity, bool isPublic): + LeaseSetDestination (isPublic), m_Owner (owner), m_Identity (identity) + { + } + I2CPSession::I2CPSession (I2CPServer& owner, std::shared_ptr socket): m_Owner (owner), m_Socket (socket), m_NextMessage (nullptr), m_NextMessageLen (0), m_NextMessageOffset (0) diff --git a/I2CP.h b/I2CP.h index cb17626d..020fd22a 100644 --- a/I2CP.h +++ b/I2CP.h @@ -5,6 +5,7 @@ #include #include #include +#include "Destination.h" namespace i2p { @@ -19,6 +20,28 @@ namespace client const uint8_t I2CP_GET_DATE_MESSAGE = 32; + class I2CPSession; + class I2CPDestination: public LeaseSetDestination + { + public: + + I2CPDestination (I2CPSession& owner, std::shared_ptr identity, bool isPublic); + + protected: + + // implements LocalDestination + std::shared_ptr GetIdentity () const { return m_Identity; }; + void Sign (const uint8_t * buf, int len, uint8_t * signature) const { /* TODO */}; + + // I2CP + void HandleDataMessage (const uint8_t * buf, size_t len) {}; + + private: + + I2CPSession& m_Owner; + std::shared_ptr m_Identity; + }; + class I2CPServer; class I2CPSession: public std::enable_shared_from_this { @@ -44,6 +67,8 @@ namespace client std::shared_ptr m_Socket; uint8_t m_Buffer[I2CP_SESSION_BUFFER_SIZE], * m_NextMessage; size_t m_NextMessageLen, m_NextMessageOffset; + + std::shared_ptr m_Destination; }; typedef void (I2CPSession::*I2CPMessageHandler)(const uint8_t * buf, size_t len); diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index e2451f68..c47a1657 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -249,7 +249,23 @@ namespace i2p return m; } - std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr leaseSet, uint32_t replyToken) + std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr leaseSet) + { + if (!leaseSet) return nullptr; + auto m = NewI2NPShortMessage (); + uint8_t * payload = m->GetPayload (); + memcpy (payload + DATABASE_STORE_KEY_OFFSET, leaseSet->GetIdentHash (), 32); + payload[DATABASE_STORE_TYPE_OFFSET] = 1; // LeaseSet + htobe32buf (payload + DATABASE_STORE_REPLY_TOKEN_OFFSET, 0); + size_t size = DATABASE_STORE_HEADER_SIZE; + memcpy (payload + size, leaseSet->GetBuffer (), leaseSet->GetBufferLen ()); + size += leaseSet->GetBufferLen (); + m->len += size; + m->FillI2NPMessageHeader (eI2NPDatabaseStore); + return m; + } + + std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr leaseSet, uint32_t replyToken, std::shared_ptr replyTunnel) { if (!leaseSet) return nullptr; auto m = NewI2NPShortMessage (); @@ -258,14 +274,13 @@ namespace i2p payload[DATABASE_STORE_TYPE_OFFSET] = 1; // LeaseSet htobe32buf (payload + DATABASE_STORE_REPLY_TOKEN_OFFSET, replyToken); size_t size = DATABASE_STORE_HEADER_SIZE; - if (replyToken) + if (replyToken && replyTunnel) { - auto leases = leaseSet->GetNonExpiredLeases (); - if (leases.size () > 0) + if (replyTunnel) { - htobe32buf (payload + size, leases[0]->tunnelID); + htobe32buf (payload + size, replyTunnel->GetNextTunnelID ()); size += 4; // reply tunnelID - memcpy (payload + size, leases[0]->tunnelGateway, 32); + memcpy (payload + size, replyTunnel->GetNextIdentHash (), 32); size += 32; // reply tunnel gateway } else diff --git a/I2NPProtocol.h b/I2NPProtocol.h index 113e8eb8..1ade55ed 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -224,7 +224,8 @@ namespace tunnel std::shared_ptr CreateDatabaseSearchReply (const i2p::data::IdentHash& ident, std::vector routers); std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr router = nullptr, uint32_t replyToken = 0); - std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr leaseSet, uint32_t replyToken = 0); + std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr leaseSet); // for floodfill only + std::shared_ptr CreateDatabaseStoreMsg (std::shared_ptr leaseSet, uint32_t replyToken = 0, std::shared_ptr replyTunnel = nullptr); bool IsRouterInfoMsg (std::shared_ptr msg); bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText); diff --git a/I2PControl.cpp b/I2PControl.cpp index ab9feeed..c87db150 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -2,8 +2,6 @@ #include #include #include -#include -#include #include #include @@ -16,6 +14,7 @@ #include "Crypto.h" #include "FS.h" #include "Log.h" +#include "HTTP.h" #include "Config.h" #include "NetDb.h" #include "RouterContext.h" @@ -189,71 +188,64 @@ namespace client if (ecode) { LogPrint (eLogError, "I2PControl: read error: ", ecode.message ()); return; + } + /* try to parse received data */ + std::stringstream json; + std::string response; + bool isHTTP = false; + if (memcmp (buf->data (), "POST", 4) == 0) { + long int remains = 0; + isHTTP = true; + i2p::http::HTTPReq req; + std::size_t len = req.parse(buf->data(), bytes_transferred); + if (len <= 0) { + LogPrint(eLogError, "I2PControl: incomplete/malformed POST request"); + return; + } + /* append to json chunk of data from 1st request */ + json.write(buf->begin() + len, bytes_transferred - len); + remains = req.length() - len; + /* if request has Content-Length header, fetch rest of data and store to json buffer */ + while (remains > 0) { + len = ((long int) buf->size() < remains) ? buf->size() : remains; + bytes_transferred = boost::asio::read (*socket, boost::asio::buffer (buf->data (), len)); + json.write(buf->begin(), bytes_transferred); + remains -= bytes_transferred; + } } else { - try - { - bool isHtml = !memcmp (buf->data (), "POST", 4); - std::stringstream ss; - ss.write (buf->data (), bytes_transferred); - if (isHtml) - { - std::string header; - size_t contentLength = 0; - while (!ss.eof () && header != "\r") - { - std::getline(ss, header); - auto colon = header.find (':'); - if (colon != std::string::npos && header.substr (0, colon) == "Content-Length") - contentLength = std::stoi (header.substr (colon + 1)); - } - if (ss.eof ()) - { - LogPrint (eLogError, "I2PControl: malformed request, HTTP header expected"); - return; // TODO: - } - std::streamoff rem = contentLength + ss.tellg () - bytes_transferred; // more bytes to read - if (rem > 0) - { - bytes_transferred = boost::asio::read (*socket, boost::asio::buffer (buf->data (), rem)); - ss.write (buf->data (), bytes_transferred); - } - } - std::ostringstream response; + json.write(buf->begin(), bytes_transferred); + } + LogPrint(eLogDebug, "I2PControl: json from request: ", json.str()); #if GCC47_BOOST149 - LogPrint (eLogError, "I2PControl: json_read is not supported due bug in boost 1.49 with gcc 4.7"); - response << "{\"id\":null,\"error\":"; - response << "{\"code\":-32603,\"message\":\"JSON requests is not supported with this version of boost\"},"; - response << "\"jsonrpc\":\"2.0\"}"; + LogPrint (eLogError, "I2PControl: json_read is not supported due bug in boost 1.49 with gcc 4.7"); + BuildErrorResponse(response, 32603, "JSON requests is not supported with this version of boost"); #else - boost::property_tree::ptree pt; - boost::property_tree::read_json (ss, pt); - - std::string id = pt.get("id"); - std::string method = pt.get("method"); - auto it = m_MethodHandlers.find (method); - if (it != m_MethodHandlers.end ()) - { - response << "{\"id\":" << id << ",\"result\":{"; - (this->*(it->second))(pt.get_child ("params"), response); - response << "},\"jsonrpc\":\"2.0\"}"; - } else { - LogPrint (eLogWarning, "I2PControl: unknown method ", method); - response << "{\"id\":null,\"error\":"; - response << "{\"code\":-32601,\"message\":\"Method not found\"},"; - response << "\"jsonrpc\":\"2.0\"}"; - } -#endif - SendResponse (socket, buf, response, isHtml); - } - catch (std::exception& ex) - { - LogPrint (eLogError, "I2PControl: exception when handle request: ", ex.what ()); - } - catch (...) - { - LogPrint (eLogError, "I2PControl: handle request unknown exception"); + /* now try to parse json itself */ + try { + boost::property_tree::ptree pt; + boost::property_tree::read_json (json, pt); + + std::string id = pt.get("id"); + std::string method = pt.get("method"); + auto it = m_MethodHandlers.find (method); + if (it != m_MethodHandlers.end ()) { + std::ostringstream ss; + ss << "{\"id\":" << id << ",\"result\":{"; + (this->*(it->second))(pt.get_child ("params"), ss); + ss << "},\"jsonrpc\":\"2.0\"}"; + response = ss.str(); + } else { + LogPrint (eLogWarning, "I2PControl: unknown method ", method); + BuildErrorResponse(response, 32601, "Method not found"); } + } catch (std::exception& ex) { + LogPrint (eLogError, "I2PControl: exception when handle request: ", ex.what ()); + BuildErrorResponse(response, 32603, ex.what()); + } catch (...) { + LogPrint (eLogError, "I2PControl: handle request unknown exception"); } +#endif + SendResponse (socket, buf, response, isHTTP); } void I2PControlService::InsertParam (std::ostringstream& ss, const std::string& name, int value) const @@ -275,27 +267,28 @@ namespace client ss << "\"" << name << "\":" << std::fixed << std::setprecision(2) << value; } + void I2PControlService::BuildErrorResponse (std::string & content, int code, const char *message) { + std::stringstream ss; + ss << "{\"id\":null,\"error\":"; + ss << "{\"code\":" << -code << ",\"message\":\"" << message << "\"},"; + ss << "\"jsonrpc\":\"2.0\"}"; + content = ss.str(); + } + void I2PControlService::SendResponse (std::shared_ptr socket, - std::shared_ptr buf, std::ostringstream& response, bool isHtml) - { - size_t len = response.str ().length (), offset = 0; - if (isHtml) - { - std::ostringstream header; - header << "HTTP/1.1 200 OK\r\n"; - header << "Connection: close\r\n"; - header << "Content-Length: " << boost::lexical_cast(len) << "\r\n"; - header << "Content-Type: application/json\r\n"; - header << "Date: "; - auto facet = new boost::local_time::local_time_facet ("%a, %d %b %Y %H:%M:%S GMT"); - header.imbue(std::locale (header.getloc(), facet)); - header << boost::posix_time::second_clock::local_time() << "\r\n"; - header << "\r\n"; - offset = header.str ().size (); - memcpy (buf->data (), header.str ().c_str (), offset); + std::shared_ptr buf, std::string& content, bool isHTTP) + { + if (isHTTP) { + i2p::http::HTTPRes res; + res.code = 200; + res.add_header("Content-Type", "application/json"); + res.add_header("Connection", "close"); + res.body = content; + std::string tmp = res.to_string(); + content = tmp; } - memcpy (buf->data () + offset, response.str ().c_str (), len); - boost::asio::async_write (*socket, boost::asio::buffer (buf->data (), offset + len), + std::copy(content.begin(), content.end(), buf->begin()); + boost::asio::async_write (*socket, boost::asio::buffer (buf->data (), content.length()), boost::asio::transfer_all (), std::bind(&I2PControlService::HandleResponseSent, this, std::placeholders::_1, std::placeholders::_2, socket, buf)); @@ -322,7 +315,7 @@ namespace client } InsertParam (results, "API", api); results << ","; - std::string token = boost::lexical_cast(i2p::util::GetSecondsSinceEpoch ()); + std::string token = std::to_string(i2p::util::GetSecondsSinceEpoch ()); m_Tokens.insert (token); InsertParam (results, "Token", token); } diff --git a/I2PControl.h b/I2PControl.h index f2e82254..bd5b9bad 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -45,8 +45,9 @@ namespace client void ReadRequest (std::shared_ptr socket); void HandleRequestReceived (const boost::system::error_code& ecode, size_t bytes_transferred, std::shared_ptr socket, std::shared_ptr buf); + void BuildErrorResponse (std::string & content, int code, const char *message); void SendResponse (std::shared_ptr socket, - std::shared_ptr buf, std::ostringstream& response, bool isHtml); + std::shared_ptr buf, std::string& response, bool isHtml); void HandleResponseSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::shared_ptr socket, std::shared_ptr buf); diff --git a/Identity.h b/Identity.h index d8abd6f4..541a7801 100644 --- a/Identity.h +++ b/Identity.h @@ -178,16 +178,12 @@ namespace data public: virtual ~LocalDestination() {}; - virtual const PrivateKeys& GetPrivateKeys () const = 0; virtual const uint8_t * GetEncryptionPrivateKey () const = 0; virtual const uint8_t * GetEncryptionPublicKey () const = 0; + virtual std::shared_ptr GetIdentity () const = 0; + virtual void Sign (const uint8_t * buf, int len, uint8_t * signature) const = 0; - std::shared_ptr GetIdentity () const { return GetPrivateKeys ().GetPublic (); }; const IdentHash& GetIdentHash () const { return GetIdentity ()->GetIdentHash (); }; - void Sign (const uint8_t * buf, int len, uint8_t * signature) const - { - GetPrivateKeys ().Sign (buf, len, signature); - }; }; } } diff --git a/LeaseSet.cpp b/LeaseSet.cpp index 5d259b33..5efe2b16 100644 --- a/LeaseSet.cpp +++ b/LeaseSet.cpp @@ -4,7 +4,7 @@ #include "Log.h" #include "Timestamp.h" #include "NetDb.h" -#include "TunnelPool.h" +#include "Tunnel.h" #include "LeaseSet.h" namespace i2p @@ -21,56 +21,6 @@ namespace data ReadFromBuffer (); } - LeaseSet::LeaseSet (std::shared_ptr pool): - m_IsValid (true), m_StoreLeases (true), m_ExpirationTime (0) - { - if (!pool) return; - // header - auto localDestination = pool->GetLocalDestination (); - if (!localDestination) - { - m_Buffer = nullptr; - m_BufferLen = 0; - m_IsValid = false; - LogPrint (eLogError, "LeaseSet: Destination for local LeaseSet doesn't exist"); - return; - } - m_Buffer = new uint8_t[MAX_LS_BUFFER_SIZE]; - m_BufferLen = localDestination->GetIdentity ()->ToBuffer (m_Buffer, MAX_LS_BUFFER_SIZE); - memcpy (m_Buffer + m_BufferLen, localDestination->GetEncryptionPublicKey (), 256); - m_BufferLen += 256; - auto signingKeyLen = localDestination->GetIdentity ()->GetSigningPublicKeyLen (); - memset (m_Buffer + m_BufferLen, 0, signingKeyLen); - m_BufferLen += signingKeyLen; - int numTunnels = pool->GetNumInboundTunnels () + 2; // 2 backup tunnels - if (numTunnels > 16) numTunnels = 16; // 16 tunnels maximum - auto tunnels = pool->GetInboundTunnels (numTunnels); - m_Buffer[m_BufferLen] = tunnels.size (); // num leases - m_BufferLen++; - // leases - auto currentTime = i2p::util::GetMillisecondsSinceEpoch (); - for (auto it: tunnels) - { - memcpy (m_Buffer + m_BufferLen, it->GetNextIdentHash (), 32); - m_BufferLen += 32; // gateway id - htobe32buf (m_Buffer + m_BufferLen, it->GetNextTunnelID ()); - m_BufferLen += 4; // tunnel id - uint64_t ts = it->GetCreationTime () + i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT - i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD; // 1 minute before expiration - ts *= 1000; // in milliseconds - if (ts > m_ExpirationTime) m_ExpirationTime = ts; - // make sure leaseset is newer than previous, but adding some time to expiration date - ts += (currentTime - it->GetCreationTime ()*1000LL)*2/i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT; // up to 2 secs - htobe64buf (m_Buffer + m_BufferLen, ts); - m_BufferLen += 8; // end date - } - // signature - localDestination->Sign (m_Buffer, m_BufferLen, m_Buffer + m_BufferLen); - m_BufferLen += localDestination->GetIdentity ()->GetSignatureLen (); - LogPrint (eLogDebug, "LeaseSet: Local LeaseSet of ", tunnels.size (), " leases created"); - - ReadFromBuffer (); - } - void LeaseSet::Update (const uint8_t * buf, size_t len) { if (len > m_BufferLen) @@ -195,7 +145,7 @@ namespace data if (size > len) return 0; uint8_t num = buf[size]; size++; // num - if (size + num*44 > len) return 0; + if (size + num*LEASE_SIZE > len) return 0; uint64_t timestamp= 0 ; for (int i = 0; i < num; i++) { @@ -243,6 +193,48 @@ namespace data if (IsEmpty ()) return true; auto ts = i2p::util::GetMillisecondsSinceEpoch (); return ts > m_ExpirationTime; + } + + LocalLeaseSet::LocalLeaseSet (std::shared_ptr identity, const uint8_t * encryptionPublicKey, std::vector > tunnels): + m_ExpirationTime (0), m_Identity (identity) + { + int num = tunnels.size (); + if (num > MAX_NUM_LEASES) num = MAX_NUM_LEASES; + // identity + auto signingKeyLen = m_Identity->GetSigningPublicKeyLen (); + m_BufferLen = m_Identity->GetFullLen () + 256 + signingKeyLen + 1 + num*LEASE_SIZE + m_Identity->GetSignatureLen (); + m_Buffer = new uint8_t[m_BufferLen]; + auto offset = m_Identity->ToBuffer (m_Buffer, m_BufferLen); + memcpy (m_Buffer + offset, encryptionPublicKey, 256); + offset += 256; + memset (m_Buffer + offset, 0, signingKeyLen); + offset += signingKeyLen; + // num leases + m_Buffer[offset] = num; + offset++; + // leases + auto currentTime = i2p::util::GetMillisecondsSinceEpoch (); + for (int i = 0; i < num; i++) + { + memcpy (m_Buffer + offset, tunnels[i]->GetNextIdentHash (), 32); + offset += 32; // gateway id + htobe32buf (m_Buffer + offset, tunnels[i]->GetNextTunnelID ()); + offset += 4; // tunnel id + uint64_t ts = tunnels[i]->GetCreationTime () + i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT - i2p::tunnel::TUNNEL_EXPIRATION_THRESHOLD; // 1 minute before expiration + ts *= 1000; // in milliseconds + if (ts > m_ExpirationTime) m_ExpirationTime = ts; + // make sure leaseset is newer than previous, but adding some time to expiration date + ts += (currentTime - tunnels[i]->GetCreationTime ()*1000LL)*2/i2p::tunnel::TUNNEL_EXPIRATION_TIMEOUT; // up to 2 secs + htobe64buf (m_Buffer + offset, ts); + offset += 8; // end date + } + // we don't sign it yet. must be signed later on + } + + bool LocalLeaseSet::IsExpired () const + { + auto ts = i2p::util::GetMillisecondsSinceEpoch (); + return ts > m_ExpirationTime; } } } diff --git a/LeaseSet.h b/LeaseSet.h index f5685210..289ae25c 100644 --- a/LeaseSet.h +++ b/LeaseSet.h @@ -12,7 +12,7 @@ namespace i2p namespace tunnel { - class TunnelPool; + class InboundTunnel; } namespace data @@ -37,14 +37,14 @@ namespace data }; }; - const int MAX_LS_BUFFER_SIZE = 3072; + const size_t MAX_LS_BUFFER_SIZE = 3072; + const size_t LEASE_SIZE = 44; // 32 + 4 + 8 const uint8_t MAX_NUM_LEASES = 16; class LeaseSet: public RoutingDestination { public: LeaseSet (const uint8_t * buf, size_t len, bool storeLeases = true); - LeaseSet (std::shared_ptr pool); ~LeaseSet () { delete[] m_Buffer; }; void Update (const uint8_t * buf, size_t len); bool IsNewer (const uint8_t * buf, size_t len) const; @@ -82,6 +82,31 @@ namespace data uint8_t * m_Buffer; size_t m_BufferLen; }; + + class LocalLeaseSet + { + public: + + LocalLeaseSet (std::shared_ptr identity, const uint8_t * encryptionPublicKey, std::vector > tunnels); + ~LocalLeaseSet () { delete[] m_Buffer; }; + + const uint8_t * GetBuffer () const { return m_Buffer; }; + uint8_t * GetSignature () { return m_Buffer + m_BufferLen - GetSignatureLen (); }; + size_t GetBufferLen () const { return m_BufferLen; }; + size_t GetSignatureLen () const { return m_Identity->GetSignatureLen (); }; + const IdentHash& GetIdentHash () const { return m_Identity->GetIdentHash (); }; + bool IsExpired () const; + bool operator== (const LeaseSet& other) const + { return m_BufferLen == other.GetBufferLen () && !memcmp (other.GetBuffer (), other.GetBuffer (), m_BufferLen); }; + + + private: + + uint64_t m_ExpirationTime; // in milliseconds + std::shared_ptr m_Identity; + uint8_t * m_Buffer; + size_t m_BufferLen; + }; } } diff --git a/RouterContext.h b/RouterContext.h index 5a72ad58..05339847 100644 --- a/RouterContext.h +++ b/RouterContext.h @@ -30,6 +30,7 @@ namespace i2p RouterContext (); void Init (); + const i2p::data::PrivateKeys& GetPrivateKeys () const { return m_Keys; }; i2p::data::RouterInfo& GetRouterInfo () { return m_RouterInfo; }; std::shared_ptr GetSharedRouterInfo () const { @@ -73,13 +74,14 @@ namespace i2p void UpdateStats (); // implements LocalDestination - const i2p::data::PrivateKeys& GetPrivateKeys () const { return m_Keys; }; + std::shared_ptr GetIdentity () const { return m_Keys.GetPublic (); }; const uint8_t * GetEncryptionPrivateKey () const { return m_Keys.GetPrivateKey (); }; const uint8_t * GetEncryptionPublicKey () const { return GetIdentity ()->GetStandardIdentity ().publicKey; }; + void Sign (const uint8_t * buf, int len, uint8_t * signature) const { m_Keys.Sign (buf, len, signature); }; void SetLeaseSetUpdated () {}; // implements GarlicDestination - std::shared_ptr GetLeaseSet () { return nullptr; }; + std::shared_ptr GetLeaseSet () { return nullptr; }; std::shared_ptr GetTunnelPool () const; void HandleI2NPMessage (const uint8_t * buf, size_t len, std::shared_ptr from); diff --git a/Tunnel.cpp b/Tunnel.cpp index bf81dc5e..ebaf98c8 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -562,7 +562,7 @@ namespace tunnel case eTunnelStatePending: if (ts > tunnel->GetCreationTime () + TUNNEL_CREATION_TIMEOUT) { - LogPrint (eLogWarning, "Tunnel: pending build request ", it->first, " timeout, deleted"); + LogPrint (eLogDebug, "Tunnel: pending build request ", it->first, " timeout, deleted"); // update stats auto config = tunnel->GetTunnelConfig (); if (config) @@ -587,7 +587,7 @@ namespace tunnel it++; break; case eTunnelStateBuildFailed: - LogPrint (eLogWarning, "Tunnel: pending build request ", it->first, " failed, deleted"); + LogPrint (eLogDebug, "Tunnel: pending build request ", it->first, " failed, deleted"); it = pendingTunnels.erase (it); m_NumFailedTunnelCreations++; break; diff --git a/debian/i2pd.1 b/debian/i2pd.1 index f61e243e..7409cd49 100644 --- a/debian/i2pd.1 +++ b/debian/i2pd.1 @@ -36,6 +36,9 @@ Where to write pidfile (don\'t write by default) \fB\-\-log=\fR Logs destination: \fIstdout\fR, \fIfile\fR, \fIsyslog\fR (\fIstdout\fR if not set, \fIfile\fR - otherwise, for compatibility) .TP +\fB\-\-logfile\fR +Path to logfile (default - autodetect) +.TP \fB\-\-loglevel=\fR Log messages above this level (\fIdebug\fR, \fBinfo\fR, \fIwarn\fR, \fIerror\fR) .TP diff --git a/tests/test-http-req.cpp b/tests/test-http-req.cpp index 484a7ad6..d5362622 100644 --- a/tests/test-http-req.cpp +++ b/tests/test-http-req.cpp @@ -3,11 +3,12 @@ using namespace i2p::http; -int main(int argc, char *argv[]) { +int main() { HTTPReq *req; int ret = 0, len = 0; const char *buf; + /* test: parsing request with body */ buf = "GET / HTTP/1.0\r\n" "User-Agent: curl/7.26.0\r\n" @@ -31,6 +32,7 @@ int main(int argc, char *argv[]) { assert(req->headers.find("User-Agent")->second == "curl/7.26.0"); delete req; + /* test: parsing request without body */ buf = "GET / HTTP/1.0\r\n" "\r\n"; @@ -44,6 +46,7 @@ int main(int argc, char *argv[]) { assert(req->headers.size() == 0); delete req; + /* test: parsing request without body */ buf = "GET / HTTP/1.1\r\n" "\r\n"; @@ -52,6 +55,7 @@ int main(int argc, char *argv[]) { assert((ret = req->parse(buf, len)) == -1); /* no host header */ delete req; + /* test: parsing incomplete request */ buf = "GET / HTTP/1.0\r\n" ""; @@ -60,9 +64,11 @@ int main(int argc, char *argv[]) { assert((ret = req->parse(buf, len)) == 0); /* request not completed */ delete req; + /* test: parsing slightly malformed request */ buf = "GET http://inr.i2p HTTP/1.1\r\n" "Host: stats.i2p\r\n" + "Accept-Encoding: \r\n" "Accept: */*\r\n" "\r\n"; len = strlen(buf); @@ -71,9 +77,13 @@ int main(int argc, char *argv[]) { assert(req->method == "GET"); assert(req->uri == "http://inr.i2p"); assert(req->host == "stats.i2p"); - assert(req->headers.size() == 2); + assert(req->headers.size() == 3); assert(req->headers.count("Host") == 1); assert(req->headers.count("Accept") == 1); + assert(req->headers.count("Accept-Encoding") == 1); + assert(req->headers["Host"] == "stats.i2p"); + assert(req->headers["Accept"] == "*/*"); + assert(req->headers["Accept-Encoding"] == ""); delete req; return 0; diff --git a/tests/test-http-res.cpp b/tests/test-http-res.cpp index 6188a68d..7dd74e1e 100644 --- a/tests/test-http-res.cpp +++ b/tests/test-http-res.cpp @@ -3,11 +3,12 @@ using namespace i2p::http; -int main(int argc, char *argv[]) { +int main() { HTTPRes *res; int ret = 0, len = 0; const char *buf; + /* test: parsing valid response without body */ buf = "HTTP/1.1 304 Not Modified\r\n" "Date: Thu, 14 Apr 2016 00:00:00 GMT\r\n" @@ -31,6 +32,18 @@ int main(int argc, char *argv[]) { assert(res->length() == 536); delete res; + /* test: building request */ + buf = + "HTTP/1.0 304 Not Modified\r\n" + "Content-Length: 0\r\n" + "\r\n"; + res = new HTTPRes; + res->version = "HTTP/1.0"; + res->code = 304; + res->status = "Not Modified"; + res->add_header("Content-Length", "0"); + assert(res->to_string() == buf); + return 0; } diff --git a/tests/test-http-url.cpp b/tests/test-http-url.cpp index 71b2f703..d574f1e2 100644 --- a/tests/test-http-url.cpp +++ b/tests/test-http-url.cpp @@ -3,7 +3,7 @@ using namespace i2p::http; -int main(int argc, char *argv[]) { +int main() { std::map params; URL *url; diff --git a/tests/test-http-url_decode.cpp b/tests/test-http-url_decode.cpp index 1c548e6f..f72b2c50 100644 --- a/tests/test-http-url_decode.cpp +++ b/tests/test-http-url_decode.cpp @@ -3,7 +3,7 @@ using namespace i2p::http; -int main(int argc, char *argv[]) { +int main() { std::string in("/%D1%81%D1%82%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D0%B0/"); std::string out = UrlDecode(in);