diff --git a/Config.cpp b/Config.cpp index 6f4014ba..9e8fb657 100644 --- a/Config.cpp +++ b/Config.cpp @@ -200,7 +200,13 @@ namespace config { ("websockets.enabled", value()->default_value(false), "enable websocket server") ("websockets.address", value()->default_value("127.0.0.1"), "address to bind websocket server on") ("websockets.port", value()->default_value(7666), "port to bind websocket server on"); - + + options_description websocks("WebSOCKS options"); + websocks.add_options() + ("websocks.enabled", value()->default_value(false), "enable WebSOCKS server") + ("websocks.address", value()->default_value("127.0.0.1"), "address to bind WebSOCKS server on") + ("websocks.port", value()->default_value(7666), "port to bind WebSOCKS server on"); + m_OptionsDesc .add(general) .add(limits) @@ -217,6 +223,7 @@ namespace config { .add(addressbook) .add(trust) .add(websocket) + .add(websocks) ; } diff --git a/Daemon.cpp b/Daemon.cpp index a007995f..edd3dcf9 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -27,6 +27,7 @@ #include "Event.h" #include "Websocket.h" +#include "WebSocks.h" namespace i2p { @@ -43,6 +44,7 @@ namespace i2p std::unique_ptr UPnP; #ifdef WITH_EVENTS std::unique_ptr m_WebsocketServer; + std::unique_ptr m_WebSocksServer; #endif }; @@ -307,6 +309,14 @@ namespace i2p d.m_WebsocketServer->Start(); i2p::event::core.SetListener(d.m_WebsocketServer->ToListener()); } + bool websocks; i2p::config::GetOption("websocks.enabled", websocks); + if (websocks) { + std::string websocksAddr; i2p::config::GetOption("websocks.address", websocksAddr); + uint16_t websocksPort; i2p::config::GetOption("websocks.port", websocksPort); + LogPrint(eLogInfo, "Daemon: starting up WebSOCKS server at ", websocksAddr, ":", websocksPort); + d.m_WebSocksServer = std::unique_ptr(new i2p::client::WebSocks(websocksAddr, websocksPort)); + d.m_WebSocksServer->Start(); + } #endif return true; diff --git a/WebSocks.cpp b/WebSocks.cpp new file mode 100644 index 00000000..6787d390 --- /dev/null +++ b/WebSocks.cpp @@ -0,0 +1,411 @@ +#include "WebSocks.h" +#include "Log.h" + +#ifdef WITH_EVENTS +#include "ClientContext.h" +#include "Identity.h" +#include "Destination.h" +#include "Streaming.h" +#include + +#include +#include + +#include +#define GCC47_BOOST149 ((BOOST_VERSION == 104900) && (__GNUC__ == 4) && (__GNUC_MINOR__ >= 7)) +#if !GCC47_BOOST149 +#include +#endif + +namespace i2p +{ +namespace client +{ + typedef websocketpp::server WebSocksServerImpl; + + typedef std::function)> StreamConnectFunc; + + + struct IWebSocksConn + { + virtual void Close() = 0; + virtual void GotMessage(const websocketpp::connection_hdl & conn, WebSocksServerImpl::message_ptr msg) = 0; + }; + + typedef std::shared_ptr WebSocksConn_ptr; + + WebSocksConn_ptr CreateWebSocksConn(const websocketpp::connection_hdl & conn, WebSocksImpl * parent); + + class WebSocksImpl + { + + typedef std::mutex mutex_t; + typedef std::unique_lock lock_t; + + typedef std::shared_ptr Destination_t; + public: + + typedef WebSocksServerImpl ServerImpl; + typedef ServerImpl::message_ptr MessagePtr; + + WebSocksImpl(const std::string & addr, int port) : + m_Run(false), + m_Addr(addr), + m_Port(port), + m_Thread(nullptr) + { + m_Server.init_asio(); + m_Server.set_open_handler(std::bind(&WebSocksImpl::ConnOpened, this, std::placeholders::_1)); + i2p::data::PrivateKeys k = i2p::data::PrivateKeys::CreateRandomKeys(i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519); + m_Dest = std::make_shared(k, false); + } + + ServerImpl::connection_ptr GetConn(const websocketpp::connection_hdl & conn) + { + return m_Server.get_con_from_hdl(conn); + } + + void CloseConn(const websocketpp::connection_hdl & conn) + { + auto c = GetConn(conn); + if(c) c->close(websocketpp::close::status::normal, "closed"); + } + + void CreateStreamTo(const std::string & addr, int port, StreamConnectFunc complete) + { + auto & addressbook = i2p::client::context.GetAddressBook(); + i2p::data::IdentHash ident; + if(addressbook.GetIdentHash(addr, ident)) { + // address found + m_Dest->CreateStream(complete, ident, port); + } else { + // not found + complete(nullptr); + } + } + + void ConnOpened(websocketpp::connection_hdl conn) + { + m_Conns.push_back(CreateWebSocksConn(conn, this)); + } + + void Start() + { + if(m_Run) return; // already started + m_Server.listen(boost::asio::ip::address::from_string(m_Addr), m_Port); + m_Server.start_accept(); + m_Run = true; + m_Thread = new std::thread([&] (){ + while(m_Run) { + try { + m_Server.run(); + } catch( std::exception & ex) { + LogPrint(eLogError, "Websocks runtime exception: ", ex.what()); + } + } + }); + m_Dest->Start(); + } + + void Stop() + { + for(const auto & conn : m_Conns) + conn->Close(); + + m_Dest->Stop(); + m_Run = false; + m_Server.stop(); + if(m_Thread) { + m_Thread->join(); + delete m_Thread; + } + m_Thread = nullptr; + } + + private: + std::vector m_Conns; + bool m_Run; + ServerImpl m_Server; + std::string m_Addr; + int m_Port; + std::thread * m_Thread; + Destination_t m_Dest; + }; + + struct WebSocksConn : public IWebSocksConn + { + enum ConnState + { + eWSCInitial, + eWSCTryConnect, + eWSCFailConnect, + eWSCOkayConnect, + eWSCClose, + eWSCEnd + }; + + typedef WebSocksServerImpl ServerImpl; + typedef ServerImpl::message_ptr Message_t; + typedef websocketpp::connection_hdl ServerConn; + typedef std::shared_ptr Destination_t; + typedef std::shared_ptr StreamDest_t; + typedef std::shared_ptr Stream_t; + + ServerConn m_Conn; + Stream_t m_Stream; + ConnState m_State; + WebSocksImpl * m_Parent; + std::string m_RemoteAddr; + int m_RemotePort; + uint8_t m_RecvBuf[2048]; + + WebSocksConn(const ServerConn & conn, WebSocksImpl * parent) : + m_Conn(conn), + m_Stream(nullptr), + m_State(eWSCInitial), + m_Parent(parent) + { + + } + + ~WebSocksConn() + { + Close(); + } + + void EnterState(ConnState state) + { + LogPrint(eLogDebug, "websocks: state ", m_State, " -> ", state); + switch(m_State) + { + case eWSCInitial: + if (state == eWSCClose) { + m_State = eWSCClose; + // connection was opened but never used + LogPrint(eLogInfo, "websocks: connection closed but never used"); + Close(); + return; + } else if (state == eWSCTryConnect) { + // we will try to connect + m_State = eWSCTryConnect; + m_Parent->CreateStreamTo(m_RemoteAddr, m_RemotePort, std::bind(&WebSocksConn::ConnectResult, this, std::placeholders::_1)); + } else { + LogPrint(eLogWarning, "websocks: invalid state change ", m_State, " -> ", state); + } + return; + case eWSCTryConnect: + if(state == eWSCOkayConnect) { + // we connected okay + LogPrint(eLogDebug, "websocks: connected to ", m_RemoteAddr, ":", m_RemotePort); + SendResponse(""); + m_State = eWSCOkayConnect; + StartForwarding(); + } else if(state == eWSCFailConnect) { + // we did not connect okay + LogPrint(eLogDebug, "websocks: failed to connect to ", m_RemoteAddr, ":", m_RemotePort); + SendResponse("failed to connect"); + m_State = eWSCFailConnect; + EnterState(eWSCInitial); + } else if(state == eWSCClose) { + // premature close + LogPrint(eLogWarning, "websocks: websocket connection closed prematurely"); + m_State = eWSCClose; + } else { + LogPrint(eLogWarning, "websocks: invalid state change ", m_State, " -> ", state); + } + return; + case eWSCFailConnect: + if (state == eWSCInitial) { + // reset to initial state so we can try connecting again + m_RemoteAddr = ""; + m_RemotePort = 0; + LogPrint(eLogDebug, "websocks: reset websocket conn to initial state"); + m_State = eWSCInitial; + } else if (state == eWSCClose) { + // we are going to close the connection + m_State = eWSCClose; + Close(); + } else { + LogPrint(eLogWarning, "websocks: invalid state change ", m_State, " -> ", state); + } + return; + case eWSCOkayConnect: + if(state == eWSCClose) { + // graceful close + m_State = eWSCClose; + Close(); + } else { + LogPrint(eLogWarning, "websocks: invalid state change ", m_State, " -> ", state); + } + case eWSCClose: + if(state == eWSCEnd) { + LogPrint(eLogDebug, "websocks: socket ended"); + } else { + LogPrint(eLogWarning, "websocks: invalid state change ", m_State, " -> ", state); + } + return; + default: + LogPrint(eLogError, "websocks: bad state ", m_State); + } + } + + void StartForwarding() + { + LogPrint(eLogDebug, "websocks: begin forwarding data"); + AsyncRecv(); + } + + void HandleAsyncRecv(const boost::system::error_code &ec, std::size_t n) + { + if(ec) { + // error + LogPrint(eLogWarning, "websocks: connection error ", ec.message()); + EnterState(eWSCClose); + } else { + // forward data + LogPrint(eLogDebug, "websocks recv ", n); + std::string str((char*)m_RecvBuf, n); + auto conn = m_Parent->GetConn(m_Conn); + if(!conn) { + LogPrint(eLogWarning, "websocks: connection is gone"); + EnterState(eWSCClose); + return; + } + conn->send(str); + AsyncRecv(); + } + } + + void AsyncRecv() + { + m_Stream->AsyncReceive( + boost::asio::buffer(m_RecvBuf, sizeof(m_RecvBuf)), + std::bind(&WebSocksConn::HandleAsyncRecv, this, std::placeholders::_1, std::placeholders::_2)); + } + + /** @brief send error message or empty string for success */ + void SendResponse(const std::string & errormsg) + { + boost::property_tree::ptree resp; + if(errormsg.size()) { + resp.put("error", errormsg); + resp.put("success", 0); + } else { + resp.put("success", 1); + } + std::ostringstream ss; + write_json(ss, resp); + auto conn = m_Parent->GetConn(m_Conn); + if(conn) conn->send(ss.str()); + } + + void ConnectResult(Stream_t stream) + { + m_Stream = stream; + if(m_State == eWSCClose) { + // premature close of websocket + Close(); + return; + } + if(m_Stream) { + // connect good + EnterState(eWSCOkayConnect); + } else { + // connect failed + EnterState(eWSCFailConnect); + } + } + + virtual void GotMessage(const websocketpp::connection_hdl & conn, WebSocksServerImpl::message_ptr msg) + { + (void) conn; + std::string payload = msg->get_payload(); + if(m_State == eWSCOkayConnect) + { + // forward to server + LogPrint(eLogDebug, "websocks: send ", payload.size()); + m_Stream->Send((uint8_t*)payload.c_str(), payload.size()); + } else if (m_State == eWSCInitial) { + // recv connect request + auto itr = payload.find(":"); + if(itr == std::string::npos) { + // no port + m_RemotePort = 0; + m_RemoteAddr = payload; + } else { + // includes port + m_RemotePort = std::stoi(payload.substr(itr+1)); + m_RemoteAddr = payload.substr(0, itr); + } + EnterState(eWSCTryConnect); + } else { + // wtf? + LogPrint(eLogWarning, "websocks: got message in invalid state ", m_State); + } + } + + virtual void Close() + { + if(m_State == eWSCClose) { + LogPrint(eLogDebug, "websocks: closing connection"); + if(m_Stream) m_Stream->Close(); + m_Parent->CloseConn(m_Conn); + EnterState(eWSCEnd); + } else { + EnterState(eWSCClose); + } + } + }; + + WebSocksConn_ptr CreateWebSocksConn(const websocketpp::connection_hdl & conn, WebSocksImpl * parent) + { + auto ptr = std::make_shared(conn, parent); + auto c = parent->GetConn(conn); + c->set_message_handler(std::bind(&WebSocksConn::GotMessage, ptr.get(), std::placeholders::_1, std::placeholders::_2)); + return ptr; + } + +} +} +#else + +// no websocket support + +namespace i2p +{ +namespace client +{ + class WebSocksImpl + { + public: + WebSocks(const std::string & addr, int port) {} + ~WebSocks(){} + void Start() + { + LogPrint(eLogInfo, "WebSockets not enabled on compile time"); + } + + void Stop() {} + }; +} +} + +#endif +namespace i2p +{ +namespace client +{ + WebSocks::WebSocks(const std::string & addr, int port) : m_Impl(new WebSocksImpl(addr, port)) {} + WebSocks::~WebSocks() { delete m_Impl; } + + void WebSocks::Start() + { + m_Impl->Start(); + } + + void WebSocks::Stop() + { + m_Impl->Stop(); + } +} +} diff --git a/WebSocks.h b/WebSocks.h new file mode 100644 index 00000000..93943abd --- /dev/null +++ b/WebSocks.h @@ -0,0 +1,27 @@ +#ifndef WEBSOCKS_H_ +#define WEBSOCKS_H_ +#include + +namespace i2p +{ +namespace client +{ + + class WebSocksImpl; + + /** @brief websocket socks proxy server */ + class WebSocks + { + public: + WebSocks(const std::string & addr, int port); + ~WebSocks(); + + void Start(); + void Stop(); + + private: + WebSocksImpl * m_Impl; + }; +} +} +#endif diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index a1fd19c2..2baa02aa 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -98,6 +98,7 @@ set (CLIENT_SRC "${CMAKE_SOURCE_DIR}/HTTP.cpp" "${CMAKE_SOURCE_DIR}/HTTPProxy.cpp" "${CMAKE_SOURCE_DIR}/I2CP.cpp" + "${CMAKE_SOURCE_DIR}/WebSocks.cpp" ) if(WITH_WEBSOCKETS) diff --git a/filelist.mk b/filelist.mk index 76f58785..d5d703a6 100644 --- a/filelist.mk +++ b/filelist.mk @@ -9,7 +9,7 @@ LIB_SRC = \ LIB_CLIENT_SRC = \ AddressBook.cpp BOB.cpp ClientContext.cpp I2PTunnel.cpp I2PService.cpp \ - SAM.cpp SOCKS.cpp HTTPProxy.cpp I2CP.cpp + SAM.cpp SOCKS.cpp HTTPProxy.cpp I2CP.cpp WebSocks.cpp # also: Daemon{Linux,Win32}.cpp will be added later DAEMON_SRC = \