Skip to content

Commit

Permalink
#26 Fix client hanging while connecting
Browse files Browse the repository at this point in the history
  • Loading branch information
securesocketfunneling committed Sep 13, 2016
1 parent 6eec259 commit 7a82a45
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 11 deletions.
5 changes: 3 additions & 2 deletions src/core/client/client.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@ class SSFClient
boost::asio::io_service& get_io_service();

private:
void NetworkToTransport(const boost::system::error_code& ec,
NetworkSocketPtr p_socket);
void NetworkToTransport(const boost::system::error_code& ec);

void DoSSFStart(NetworkSocketPtr p_socket,
const boost::system::error_code& ec);
Expand All @@ -67,6 +66,8 @@ class SSFClient
BaseUserServicePtr p_user_service, boost::system::error_code ec);

private:
NetworkSocketPtr p_socket_;

AsyncEngine async_engine_;

Demux fiber_demux_;
Expand Down
27 changes: 18 additions & 9 deletions src/core/client/client.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ SSFClient<N, T>::SSFClient(std::vector<BaseUserServicePtr> user_services,
ClientCallback callback)
: T<typename N::socket>(
boost::bind(&SSFClient<N, T>::DoSSFStart, this, _1, _2)),
p_socket_(nullptr),
async_engine_(),
fiber_demux_(async_engine_.get_io_service()),
user_services_(user_services),
Expand All @@ -51,8 +52,7 @@ void SSFClient<N, T>::Run(const NetworkQuery& query,
}

// Create network socket
NetworkSocketPtr p_socket =
std::make_shared<NetworkSocket>(async_engine_.get_io_service());
p_socket_ = std::make_shared<NetworkSocket>(async_engine_.get_io_service());

// resolve remote endpoint with query
NetworkResolver resolver(async_engine_.get_io_service());
Expand All @@ -66,13 +66,23 @@ void SSFClient<N, T>::Run(const NetworkQuery& query,
async_engine_.Start();

// async connect client to given endpoint
p_socket->async_connect(
p_socket_->async_connect(
*endpoint_it,
boost::bind(&SSFClient<N, T>::NetworkToTransport, this, _1, p_socket));
boost::bind(&SSFClient<N, T>::NetworkToTransport, this, _1));
}

template <class N, template <class> class T>
void SSFClient<N, T>::Stop() {
if (!async_engine_.IsStarted()) {
return;
}

if (p_socket_.get() != nullptr) {
boost::system::error_code close_ec;
p_socket_->close(close_ec);
p_socket_.reset();
}

fiber_demux_.close();

async_engine_.Stop();
Expand All @@ -84,19 +94,18 @@ boost::asio::io_service& SSFClient<N, T>::get_io_service() {
}

template <class N, template <class> class T>
void SSFClient<N, T>::NetworkToTransport(const boost::system::error_code& ec,
NetworkSocketPtr p_socket) {
void SSFClient<N, T>::NetworkToTransport(const boost::system::error_code& ec) {
if (!ec) {
this->DoSSFInitiate(p_socket);
this->DoSSFInitiate(std::move(p_socket_));
return;
}

SSF_LOG(kLogError) << "client: error when connecting to server: "
<< ec.message();

if (p_socket) {
if (p_socket_) {
boost::system::error_code close_ec;
p_socket->close(close_ec);
p_socket_->close(close_ec);
}

Notify(ssf::services::initialisation::NETWORK, nullptr, ec);
Expand Down
21 changes: 21 additions & 0 deletions src/tests/network/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,27 @@ add_target("ssf_client_server_cipher_suites_tests"
)
project_group(${network_tests_group_name} ssf_client_server_cipher_suites_tests)

# --- SSF Client tests
set(ssf_client_source_files
"ssf_client_tests.cpp"
${SSF_SOURCES}
)

add_target("ssf_client_tests"
TYPE
executable ${EXEC_FLAG} TEST
LINKS
${OpenSSL_LIBRARIES}
${Boost_LIBRARIES}
${PLATFORM_SPECIFIC_LIB_DEP}
lib_ssf_network
PREFIX_SKIP .*/src
HEADER_FILTER "\\.h(h|m|pp|xx|\\+\\+)?"
FILES
${ssf_client_source_files}
)
project_group(${network_tests_group_name} ssf_client_tests)

# --- SSF Server tests
set(ssf_server_source_files
"ssf_server_tests.cpp"
Expand Down
117 changes: 117 additions & 0 deletions src/tests/network/ssf_client_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#include <condition_variable>
#include <mutex>
#include <vector>

#include <boost/asio/steady_timer.hpp>
#include <boost/asio/io_service.hpp>
#include <boost/asio/ip/tcp.hpp>

#include <gtest/gtest.h>

#include "common/config/config.h"

#include "core/network_protocol.h"
#include "core/transport_virtual_layer_policies/transport_protocol_policy.h"
#include "core/client/client.h"

using NetworkProtocol = ssf::network::NetworkProtocol;
using Client =
ssf::SSFClient<NetworkProtocol::Protocol, ssf::TransportProtocolPolicy>;
using Demux = Client::Demux;
using BaseUserServicePtr =
ssf::services::BaseUserService<Demux>::BaseUserServicePtr;

void InitTCPServer(boost::asio::ip::tcp::acceptor& server, int server_port);

TEST(SSFClientTest, CloseWhileConnecting) {
ssf::AsyncEngine async_engine;

std::condition_variable wait_stop_cv;
std::mutex mutex;
bool stopped = false;

// End test callback
auto end_test = [&wait_stop_cv, &mutex, &stopped](bool status) {
{
boost::lock_guard<std::mutex> lock(mutex);
stopped = status;
}
wait_stop_cv.notify_all();
};

// Init server
int server_port = 15000;
boost::asio::ip::tcp::acceptor server(async_engine.get_io_service());
InitTCPServer(server, 15000);

// Init timer (if client hangs)
boost::system::error_code timer_ec;
boost::asio::steady_timer timer(async_engine.get_io_service());
timer.expires_from_now(std::chrono::seconds(5), timer_ec);
ASSERT_EQ(0, timer_ec.value());
timer.async_wait([&end_test](const boost::system::error_code& ec) {
EXPECT_NE(0, ec.value()) << "Timer should be canceled. Client is hanging";
if (!ec) {
end_test(false);
}
});

// Init client
std::vector<BaseUserServicePtr> client_options;
ssf::config::Config ssf_config;
ssf_config.Init();

auto endpoint_query = NetworkProtocol::GenerateClientQuery(
"127.0.0.1", std::to_string(server_port), ssf_config, {});

auto callback = [&end_test](ssf::services::initialisation::type type,
BaseUserServicePtr p_user_service,
const boost::system::error_code& ec) {
EXPECT_EQ(ssf::services::initialisation::NETWORK, type);
EXPECT_NE(0, ec.value());
end_test(ec.value() != 0);
};
Client client(client_options, ssf_config.services(), callback);

boost::system::error_code run_ec;

// Connect client to server
client.Run(endpoint_query, run_ec);
ASSERT_EQ(0, run_ec.value());

// Wait new server connection
async_engine.Start();
ASSERT_TRUE(async_engine.IsStarted());
boost::asio::ip::tcp::socket socket(async_engine.get_io_service());
server.async_accept(socket, [&client](const boost::system::error_code& ec) {
EXPECT_EQ(0, ec.value()) << "Accept connection in error";
// Stop client while connecting
client.Stop();
});

// Wait client action
std::unique_lock<std::mutex> lock(mutex);
wait_stop_cv.wait(lock);
lock.unlock();

EXPECT_TRUE(stopped) << "Stop failed";

timer.cancel(timer_ec);
boost::system::error_code close_ec;
socket.close(close_ec);
server.close(close_ec);

async_engine.Stop();
}

void InitTCPServer(boost::asio::ip::tcp::acceptor& server, int server_port) {
boost::system::error_code server_ec;
boost::asio::ip::tcp::endpoint server_ep(boost::asio::ip::tcp::v4(),
server_port);
server.open(boost::asio::ip::tcp::v4(), server_ec);
ASSERT_EQ(0, server_ec.value()) << "Could not open server acceptor";
server.bind(server_ep, server_ec);
ASSERT_EQ(0, server_ec.value()) << "Could not bind server acceptor";
server.listen(boost::asio::socket_base::max_connections, server_ec);
ASSERT_EQ(0, server_ec.value()) << "Server acceptor could not listen";
}

0 comments on commit 7a82a45

Please sign in to comment.