I am learning TCP Socket Windows programing with Boost/OpenSSL/VS2019. I guess the below code is a typical server example that echoes received data to the client, it worked as expected when tested with client program. But when tested with a browser such as Chrome or IE11(set proxy IP and port 443 in the browsers), the handshake part always failed with message (handshake failed) "https proxy request (SSL routines, ssl3_get_record) [asio.ssl:336130203]". I generated Self-signed Certificate using OpenSSL for testing, and used it in the client program in the way like ssl_context.load_verify_file("cert.pem"), the test turned out to be good. Then I installed "cert.pem" in IE(and Chrome), set the proxy IP and port 443, I was expecting that the handshake will pass, but unfortunately it always failed with the above message.
My Questions are,
In order to make browsers work(the handshake part for now), the domain name or IP, or any other identification info in the Self-signed Certificate has to be accurate to the "proxy" server where I am testing on? I generated a dummy certificate for testing, it has nothing to do with the machine I am testing on. The client program(sends message to the server and receives the message sent back from the server) worked OK with it.
I am trying to implement a simple HTTPS relay, I understand there are many things need to do, my HTTP proxy works so far. To incorporate SSL, the first thing I want to ensure the proxy server can accept browsers' connections thru HTTPS/Proxy request, then transport data back and forth. Is there anything specific in the browser for HTTPS proxy request that failed the handshake? Or am I in the wrong way to build HTTPS/SSL relay? Thank you very much.
class session
{
public:
session(boost::asio::io_service& io_service,
boost::asio::ssl::context& context)
: socket_(io_service, context)
{
}
ssl_socket::lowest_layer_type& socket()
{
return socket_.lowest_layer();
}
void start()
{
socket_.async_handshake(boost::asio::ssl::stream_base::server,
boost::bind(&session::handle_handshake, this,
boost::asio::placeholders::error));
}
void handle_handshake(const boost::system::error_code& error)
{
if (!error)
{
std::cout << "handshake good" << std::endl;
socket_.async_read_some(boost::asio::buffer(data_, max_length),
boost::bind(&session::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else
{
std::cout << "handshake failed " + error.what() << std::endl;
delete this;
}
}
void handle_read(const boost::system::error_code& error,
size_t bytes_transferred)
{
if (!error)
{
boost::asio::async_write(socket_,
boost::asio::buffer(data_, bytes_transferred),
boost::bind(&session::handle_write, this,
boost::asio::placeholders::error));
}
else
{
delete this;
}
}
void handle_write(const boost::system::error_code& error)
{
if (!error)
{
socket_.async_read_some(boost::asio::buffer(data_, max_length),
boost::bind(&session::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else
{
delete this;
}
}
private:
ssl_socket socket_;
enum { max_length = 1024 };
char data_[max_length];
};
class server
{
public:
server(boost::asio::io_service& io_service, unsigned short port)
: io_service_(io_service),
acceptor_(io_service,
boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)),
context_(boost::asio::ssl::context::sslv23)
{
context_.set_options(
boost::asio::ssl::context::default_workarounds
| boost::asio::ssl::context::no_sslv2
| boost::asio::ssl::context::single_dh_use);
context_.set_password_callback(boost::bind(&server::get_password, this));
context_.use_certificate_chain_file("cert.pem");
context_.use_private_key_file("key.pem", boost::asio::ssl::context::pem);
context_.use_tmp_dh_file("dh2048.pem");
start_accept();
}
std::string get_password() const
{
return "test";
}
void start_accept()
{
session* new_session = new session(io_service_, context_);
acceptor_.async_accept(new_session->socket(),
boost::bind(&server::handle_accept, this, new_session,
boost::asio::placeholders::error));
}
void handle_accept(session* new_session,
const boost::system::error_code& error)
{
if (!error)
{
std::cout << "accept good" << std::endl;
new_session->start();
}
else
{
delete new_session;
}
start_accept();
}
private:
boost::asio::io_service& io_service_;
boost::asio::ip::tcp::acceptor acceptor_;
boost::asio::ssl::context context_;
};
Tried:
Generated self-signed Certificate files(cert.pem, key.pem, dh2048.pem) using OpenSSL
Build Server and client Program with the above certificates, Server listening on port 443, Client and server is on the same machine. Handshake between client and server went thru, message sent between client and server went thru. Moved the client to a separate PC, it works as well.
Installed the above certificate in IE and Chrome on the same machine.
Connect browsers with the server via HTTPS proxy on port 443, Handshake failed.
Expected:
Handshake between Browsers and "the Proxy" server goes thru without an error.
Actually Resulted:
browser Handshake failed with message "https proxy request (SSL routines, ssl3_get_record) [asio.ssl:336130203]"
Your server is not an HTTP server. You don't disclose what kind of proxy you're using but, if you just point e.g. Chrome at your server, it doesn't like being treated that way either.
Here's with the code made self-contained:
I'd suggest starting out with at least a valid HTTP response, e.g.:
void handle_read(error_code error, size_t bytes_transferred) {
if (!error) {
res_ = {http::status::ok, 10,
std::string(data_.data(), bytes_transferred)};
http::async_write(socket_, res_,
boost::bind(&session::handle_write, this,
asio::placeholders::error));
} else {
delete this;
}
}
This would echo minimal valid HTTP responses ignoring the request:
HTTP/1.0 200 OK
foo
bar
HTTP/1.0 200 OK
bar
qux
HTTP/1.0 200 OK
qux
We're ignoring the client's request, and we're not warning about content-length or keepalive. Le'ts improve that by going HTTP/1.1:
void handle_read(error_code error, size_t bytes_transferred) {
if (!error) {
res_ = {http::status::ok, 11,
std::string(data_.data(), bytes_transferred)};
res_.keep_alive(false);
res_.prepare_payload();
http::async_write(socket_, res_,
boost::bind(&session::handle_write, this,
asio::placeholders::error));
} else {
delete this;
}
}
Now we get responses like on openssl s_client -connect localhost:8989 -quiet -verify_quiet <<< "Hello world":
HTTP/1.1 200 OK
Connection: close
Content-Length: 12
Hello world
Does the browser like it better?
Curl doesn't complain:
curl -k https://localhost:8989/my/page
GET /my/page HTTP/1.1
Host: localhost:8989
User-Agent: curl/7.81.0
Accept: */*
My browser browser sends a load of cookies to the localhost domain:
Not shown in the browser are the actual response headers:
HTTP/1.1 200 OK
Connection: close
Content-Length: 1003
In fact, if the request is larger than 1024 bytes, the full request can't even be received before the server blurts out a partial "echo" and disconnects. Let's improve the situation by at least reading the entire request headers:
asio::async_read_until(
socket_, asio::dynamic_buffer(data_), "\r\n\r\n",
boost::bind(&session::handle_read, this,
asio::placeholders::error,
asio::placeholders::bytes_transferred));
See it In Full On Coliru
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/bind/bind.hpp>
#include <iostream>
#include <boost/beast.hpp>
namespace asio = boost::asio;
namespace ssl = asio::ssl;
namespace beast = boost::beast;
namespace http = beast::http;
using asio::ip::tcp;
using boost::system::error_code;
using ssl_socket = ssl::stream<tcp::socket>;
using namespace std::chrono_literals;
class session {
public:
session(asio::io_service& io_service, ssl::context& context)
: socket_(io_service, context) {}
ssl_socket::lowest_layer_type& socket() {
return socket_.lowest_layer();
}
void start() {
socket_.async_handshake( //
ssl_socket::server,
boost::bind(&session::handle_handshake, this,
asio::placeholders::error));
}
void handle_handshake(error_code error) {
if (!error) {
std::cout << "handshake good" << std::endl;
asio::async_read_until(
socket_, asio::dynamic_buffer(data_), "\r\n\r\n",
boost::bind(&session::handle_read, this,
asio::placeholders::error,
asio::placeholders::bytes_transferred));
} else {
std::cout << "handshake failed " + error.what() << std::endl;
delete this;
}
}
void handle_read(error_code error, size_t bytes_transferred) {
if (!error) {
res_ = {http::status::ok, 11,
std::string(data_.data(), bytes_transferred)};
res_.keep_alive(false);
res_.prepare_payload();
http::async_write(socket_, res_,
boost::bind(&session::handle_write, this,
asio::placeholders::error));
} else {
delete this;
}
}
void handle_write(error_code error) {
if (!error) {
socket_.async_read_some( //
asio::buffer(data_),
boost::bind(&session::handle_read, this,
asio::placeholders::error,
asio::placeholders::bytes_transferred));
} else {
delete this;
}
}
private:
ssl_socket socket_;
std::string data_;
http::response<http::string_body> res_;
};
class server {
public:
using Ctx = ssl::context;
server(asio::io_service& io_service, uint16_t port)
: io_service_(io_service)
, acceptor_(io_service, {tcp::v4(), port})
, context_(Ctx::sslv23) //
{
acceptor_.set_option(tcp::acceptor::reuse_address(true));
context_.set_options(Ctx::default_workarounds | Ctx::no_sslv2 |
Ctx::single_dh_use);
context_.set_password_callback(&server::get_password);
context_.use_certificate_chain_file("cert.pem");
context_.use_private_key_file("key.pem", Ctx::pem);
context_.use_tmp_dh_file("dh2048.pem");
start_accept();
}
private:
static std::string get_password(size_t, Ctx::password_purpose) {
return "test";
}
void start_accept() {
session* new_session = new session(io_service_, context_);
acceptor_.async_accept(new_session->socket(),
boost::bind(&server::handle_accept, this,
new_session,
asio::placeholders::error));
}
void handle_accept(session* new_session, error_code error) {
if (!error) {
std::cout << "accept good" << std::endl;
new_session->start();
} else {
delete new_session;
}
start_accept();
}
private:
asio::io_service& io_service_;
tcp::acceptor acceptor_;
ssl::context context_;
};
int main() {
asio::io_service ioc;
server s(ioc, 8989);
ioc.run_for(30s);
}
Further Work
In fact, you probably need to read the HTTP request anyways (since the context is browsers and HTTP proxies). So, perhaps use Beast again:
void do_receive() {
http::async_read(
socket_, buf_, req_,
boost::bind(&session::handle_read, this,
asio::placeholders::error,
asio::placeholders::bytes_transferred));
}
Now we can get rid of the keep_alive(false) since we don't clobber our input.
Now, the delete this anti-pattern can be replaced by the enable_shared_from_this pattern.
If we now move the socket creation to the server and avoid passing io_service& references, we can also remove the violation of encapsulation that was socket() and stop depending on io_service which has been deprecated for several Asio versions.
The result is eerily close to the Beast HTTP server examples:
Live On Coliru
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/bind/bind.hpp>
#include <iostream>
#include <boost/beast.hpp>
#include <boost/lexical_cast.hpp> // for the request echo
namespace asio = boost::asio;
namespace ssl = asio::ssl;
namespace beast = boost::beast;
namespace http = beast::http;
using asio::ip::tcp;
using boost::system::error_code;
using ssl_socket = ssl::stream<tcp::socket>;
using namespace std::chrono_literals;
struct session : public std::enable_shared_from_this<session> {
session(tcp::socket s, ssl::context& context)
: socket_(std::move(s), context) {}
void start() {
socket_.async_handshake( //
ssl_socket::server,
boost::bind(&session::handle_handshake, shared_from_this(),
asio::placeholders::error));
}
private:
void handle_handshake(error_code error) {
if (!error) {
std::cout << "handshake good" << std::endl;
do_receive();
} else {
std::cout << "handshake failed " + error.what() << std::endl;
}
}
void do_receive() {
http::async_read(
socket_, buf_, req_,
boost::bind(&session::handle_read, shared_from_this(),
asio::placeholders::error,
asio::placeholders::bytes_transferred));
}
void handle_read(error_code error, size_t /*bytes_transferred*/) {
if (!error) {
res_ = {http::status::ok, 11,
boost::lexical_cast<std::string>(req_)};
res_.keep_alive(false);
res_.prepare_payload();
http::async_write(socket_, res_,
boost::bind(&session::handle_write,
shared_from_this(),
asio::placeholders::error));
}
}
void handle_write(error_code error) {
if (!error) {
do_receive();
}
}
private:
ssl_socket socket_;
beast::flat_buffer buf_;
http::request<http::string_body> req_;
http::response<http::string_body> res_;
};
struct server {
server(asio::any_io_executor ex, uint16_t port)
: acceptor_(ex, {tcp::v4(), port})
, context_(Ctx::sslv23) //
{
acceptor_.set_option(tcp::acceptor::reuse_address(true));
context_.set_options(Ctx::default_workarounds | Ctx::no_sslv2 |
Ctx::single_dh_use);
context_.set_password_callback(&server::get_password);
context_.use_certificate_chain_file("cert.pem");
context_.use_private_key_file("key.pem", Ctx::pem);
context_.use_tmp_dh_file("dh2048.pem");
start_accept();
}
private:
using Ctx = ssl::context;
tcp::acceptor acceptor_;
Ctx context_;
static std::string get_password(size_t, Ctx::password_purpose) {
return "test";
}
void start_accept() {
acceptor_.async_accept(
// make_strand(acceptor_.get_executor()), // for multi-threaded servers
[this](error_code ec, tcp::socket s) {
if (!ec) {
std::cout << "accept good" << std::endl;
auto sess = std::make_shared<session>(std::move(s),
context_);
sess->start();
}
start_accept();
});
}
};
int main() {
asio::io_context ioc;
server s(ioc.get_executor(), 8989);
ioc.run_for(30s);
}
UPDATE
Saving information from the comments for the future:
Here's my review of your code, same but 100 lines of code less.
Adding back 50 lines I implemented the minimal CONNECT parsing and connect code: http://coliru.stacked-crooked.com/a/28fbdaf23ab00586 - See it working with
curl -px http://localhost:8989
on my system, for both HTTP and HTTPS targets:
Listing: Full HTTTP CONNECT de,p supporting any protocol (HTTPS included)
#include <boost/asio.hpp>
#include <boost/beast.hpp>
#include <boost/beast/http.hpp>
#include <boost/bind/bind.hpp>
#include <iomanip>
#include <iostream>
namespace Tcp {
namespace asio = boost::asio;
namespace ph = asio::placeholders;
using tcp = asio::ip::tcp;
using socket_type = tcp::socket;
using error_code = boost::system::error_code;
template <typename Session> struct Listener {
Listener(asio::any_io_executor ex, uint16_t bind_port)
: acceptor_(ex, tcp::endpoint({}, bind_port)) {}
void do_accept() {
acceptor_.async_accept( //
make_strand(acceptor_.get_executor()), [this](error_code ec, tcp::socket s) {
std::cerr << "accepted " << s.remote_endpoint() << " (" << ec.message() << ")" << std::endl;
if (!ec) {
std::make_shared<Session>(std::move(s))->start();
do_accept();
} else {
std::cerr << "do_accept: " << ec.message() << std::endl;
}
});
}
private:
tcp::acceptor acceptor_;
};
namespace util {
// Relay from from_ to to_ sockets
// Lifetime shared with shared owner
struct half_duplex {
using Owner = std::weak_ptr<void>;
half_duplex(socket_type& f, socket_type& t) : from_(f), to_(t) {}
void start(Owner w) {
owner_ = w;
do_read();
}
// send previously received pending data first
template <typename ConstBufferSequence> void start(Owner w, ConstBufferSequence pending) {
owner_ = w;
async_write(to_, pending, boost::bind(&half_duplex::on_written, owner_shared(), ph::error));
}
private:
socket_type& from_;
socket_type& to_;
Owner owner_;
std::array<uint8_t, 8192> buf_;
void do_read() {
from_.async_read_some(
asio::buffer(buf_),
boost::bind(&half_duplex::on_read, owner_shared(), ph::error, ph::bytes_transferred));
}
void on_read(error_code ec, size_t xfer) {
if (!ec)
async_write(to_, asio::buffer(buf_, xfer),
boost::bind(&half_duplex::on_written, owner_shared(), ph::error));
}
void on_written(error_code ec) {
if (!ec)
do_read();
}
std::shared_ptr<half_duplex> owner_shared() {
if (auto o = owner_.lock())
return std::shared_ptr<half_duplex>(o, this); // aliasing constructor
else
throw std::bad_weak_ptr();
}
};
} // namespace util
namespace proxy {
namespace http = boost::beast::http;
struct Session : std::enable_shared_from_this<Session> {
Session(socket_type s) : client_(std::move(s)), server_(s.get_executor()) {}
void start() {
http::async_read(
client_, lead_in_, req_,
boost::bind(&Session::on_connect_request, shared_from_this(), ph::error));
}
private:
asio::streambuf lead_in_;
http::request<http::empty_body> req_;
http::response<http::empty_body> res_;
socket_type client_, server_;
util::half_duplex down_stream_{server_, client_};
util::half_duplex up_stream_{client_, server_};
void on_connect_request(error_code ec) {
if (ec.failed() || req_.method() != http::verb::connect)
return; // TODO error handling
std::cerr << "Connect request: " << req_ << std::endl;
// TODO handle headers?
std::string upstream(req_.target());
auto pos = upstream.find_last_of(":");
auto host = upstream.substr(0, pos);
auto svc = upstream.substr(pos + 1);
if (svc.empty())
svc = "http";
// TODO async resolve?
auto eps = tcp::resolver(server_.get_executor()).resolve(host, svc);
asio::async_connect(server_, eps,
boost::bind(&Session::on_connect, shared_from_this(), ph::error));
}
void on_connect(error_code ec) {
if (ec)
return; // TODO error handling
std::cerr << "Connected to " << server_.remote_endpoint() << std::endl;
res_ = {http::status::ok, req_.version()};
res_.keep_alive(true);
res_.prepare_payload();
http::async_write(client_, res_,
boost::bind(&Session::on_connect_response, shared_from_this(), ph::error));
}
void on_connect_response(error_code ec) {
if (ec)
return; // TODO error handling
up_stream_.start(shared_from_this());
if (lead_in_.size())
down_stream_.start(shared_from_this(), lead_in_.data());
else
down_stream_.start(shared_from_this());
}
};
}
using Proxy = Listener<proxy::Session>;
} // namespace Tcp
int main(int argc, char* argv[]) {
if (argc != 2) {
std::cerr << "usage: " << std::quoted(argv[0]) << " <bind port>\n";
return 1;
}
auto bind_port = static_cast<uint16_t>(::atoi(argv[1]));
try {
boost::asio::io_context ioc;
Tcp::Proxy p(ioc.get_executor(), bind_port);
p.do_accept();
ioc.run();
} catch (std::exception const& e) {
std::cerr << "main: " << e.what() << std::endl;
return 1;
}
}
This code is intended to receive UDP multicast messages using Boost.Asio. A Boost system_error exception is thrown by the code below when the second set_option() call inside receiver's constructor is made (to join the multicast group). The complaint is "Invalid argument". This seems to be related to the fact that the constructor occurs inside a lambda defined inside IO::doIO(), because using a member for the std::thread with identical functionality (IO::threadFunc()) instead results in the expected behavior (no exceptions thrown).
Why is this, and how can I fix it so that I may use a lambda?
//g++ -std=c++11 doesntWork.cc -lboost_system -lpthread
#include <iostream>
#include <thread>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
class IO
{
public:
class receiver
{
public:
receiver(
boost::asio::io_service &io_service,
const boost::asio::ip::address &multicast_address,
const unsigned short portNumber) : _socket(io_service)
{
const boost::asio::ip::udp::endpoint listen_endpoint(
boost::asio::ip::address::from_string("0.0.0.0"), portNumber);
_socket.open(listen_endpoint.protocol());
_socket.set_option(boost::asio::ip::udp::socket::reuse_address(true));
_socket.bind(listen_endpoint);
std::cerr << " About to set option join_group" << std::endl;
_socket.set_option(boost::asio::ip::multicast::join_group(
multicast_address));
_socket.async_receive_from(
boost::asio::buffer(_data),
_sender_endpoint,
boost::bind(&receiver::handle_receive_from, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
private:
void handle_receive_from(
const boost::system::error_code &error,
const size_t bytes_recvd)
{
if (!error)
{
for(const auto &c : _data)
std::cout << c;
std::cout << std::endl;
}
}
private:
boost::asio::ip::udp::socket _socket;
boost::asio::ip::udp::endpoint _sender_endpoint;
std::vector<unsigned char> _data;
}; // receiver class
void doIO()
{
const boost::asio::ip::address multicast_address =
boost::asio::ip::address::from_string("235.0.0.1");
const unsigned short portNumber = 9999;
// _io_service_thread = std::thread(
// &IO::threadFunc, this, multicast_address, portNumber);
_io_service_thread = std::thread([&, this]{
try {
// Construct an asynchronous receiver
receiver r(_io_service, multicast_address, portNumber);
// Now run the IO service
_io_service.run();
}
catch(const boost::system::system_error &e)
{
std::cerr << e.what() << std::endl;
throw e; //std::terminate()
}
});
}
void threadFunc(
const boost::asio::ip::address &multicast_address,
const unsigned short portNumber)
{
try {
// Construct an asynchronous receiver
receiver r(_io_service, multicast_address, portNumber);
// Now run the IO service
_io_service.run();
}
catch(const boost::system::system_error &e)
{
std::cerr << e.what() << std::endl;
throw e; //std::terminate()
}
}
private:
boost::asio::io_service _io_service;
std::thread _io_service_thread;
}; // IO class
int main()
{
IO io;
io.doIO();
std::cout << "IO Service is running" << std::endl;
sleep(9999);
}
There is a race condition that can result in dangling references being accessed, invoking undefined behavior. The lambda capture-list is capturing the automatic variables, multicast_address and portNumber, by reference. However, the lifetime of these objects may end before their usage within _io_service_thread:
void doIO()
{
const boost::asio::ip::address multicast_address = /* ... */;
const unsigned short portNumber = /* ... */;
_io_service_thread = std::thread([&, this] {
// multicast_address and portNumber's lifetime may have already ended.
receiver r(_io_service, multicast_address, portNumber);
// ...
});
} // multicast_address and portNumber are destroyed.
To resolve this, consider capturing by value so that the thread operates on copies whose lifetimes will remain valid until the end of the thread. Change:
std::thread([&, this] { /* ... */ }
to:
std::thread([=] { /* ... */ }
This issue does not present itself when std::thread is constructed with the function and all its arguments, as the std::thread constructor will copy/move all provided arguments into thread-accessible storage.
Also, be aware of the destruction of the _io_service_thread object will invoke std::terminate() if it is still joinable within IO's destructor. To avoid this behavior, consider explicitly joining the _io_service_thread from the main thread.
I thought I had found the answer in the following example, but not quite.
boost::asio::ip::udp::socket socket(io_service);
...
boost::asio::ip::address_v4 local_interface =
boost::asio::ip::address_v4::from_string("1.2.3.4");
boost::asio::ip::multicast::outbound_interface option(local_interface);
socket.set_option(option);
How do I map eth0 to the appropriate outbound_interface option?
The following code works fine on Windows and Mac OS X:
const ip::udp::resolver::query queryIF( ip::udp::v4(),
_description->getInterface(), "0" );
const ip::udp::resolver::iterator interfaceIP =
resolver.resolve( queryIF );
if( interfaceIP == end )
return false;
const ip::address ifAddr( ip::udp::endpoint( *interfaceIP ).address( ));
_read->set_option( ip::multicast::join_group( mcAddr.to_v4(),
ifAddr.to_v4( )));
_write->set_option( ip::multicast::outbound_interface( ifAddr.to_v4()));
EDIT: I had some issues on Linux, but did not look into it yet. My guess is the socket option is ignored in favor of the routing table.
I think the reason why your example and eile's example don't work is because you didn't set the SO_BINDTODEVICE socket option.
See this to know why it doesn't work: http://codingrelic.geekhold.com/2009/10/code-snippet-sobindtodevice.html
See this to know how to do it with boost::asio: http://permalink.gmane.org/gmane.comp.lib.boost.asio.user/2724
/**************************************************************************//**
\brief
\details
*******************************************************************************/
class UDPClient : public BoostSocketClient
{
public:
UDPClient ();
virtual ~UDPClient();
virtual ARLErrorCode_e open(int port_num, const char* network_type="ipv4", const char* ip_address="", uint32_t listen_interface=0);
virtual ARLErrorCode_e send(u8* message, u32 size);
virtual ARLErrorCode_e close();
virtual bool isOpen();
//virtual void onReceived(u8*, u32);
private:
void startReceive();
void handleReceive(const boost::system::error_code&, std::size_t);
void handleSend(const boost::system::error_code& error, std::size_t bytes_transferred);
private:
boost::asio::io_service send_ios_;
std::unique_ptr<boost::asio::io_service::work> send_worker_;
boost::asio::io_service receive_ios_;
std::unique_ptr<boost::asio::io_service::work> receive_worker_;
boost::thread send_thread_;
boost::thread receive_thread_;
boost::array<u8, 1024> _buffer;
boost::asio::ip::udp::endpoint send_endpoint_;
boost::asio::ip::udp::endpoint sender_endpoint_;
boost::asio::ip::udp::endpoint listen_endpoint_;
std::unique_ptr<boost::asio::ip::udp::socket> send_udp_socket_;
std::unique_ptr<boost::asio::ip::udp::socket> receive_udp_socket_;
};
#include <ACCompLib/Include/Typedefs.h>
#include <ACCompLib/Include/ARLErrorCodes.h>
#include <NetLib/Platform/Boost/cpp/UDPClient.h>
#include "Ws2tcpip.h"
#include "Iphlpapi.h"
using namespace std;
using namespace boost;
using namespace asio;
using namespace ip;
using namespace NetLib;
/***************************************************************************//**
\brief Constructor
\details
*******************************************************************************/
UDPClient::UDPClient()
{
receive_worker_.reset(new boost::asio::io_service::work(receive_ios_));
}
/***************************************************************************//**
\brief ctor
\details
*******************************************************************************/
UDPClient::~UDPClient()
{
try
{
receive_worker_.reset();
//send_worker_.reset();
if (send_thread_.joinable()) {
send_thread_.join();
}
if (receive_thread_.joinable()) {
receive_thread_.join();
}
}
catch (std::exception& e)
{
std::string str = e.what();
}
}
/***************************************************************************//**
\brief
\details
\note
\param[in]
*******************************************************************************/
ARLErrorCode_e UDPClient::open(int port_num, const char* network_type, const char* multicastAddress, uint32_t listen_interface)
{
try
{
struct in_addr in;
in.S_un.S_addr = listen_interface;
char* address_listen = inet_ntoa(in);
//const char* address_listen = "0.0.0.0";
std::string address_mcast = multicastAddress;
unsigned short address_port = port_num;
boost::system::error_code ec;
boost::asio::ip::address listen_addr = boost::asio::ip::address::from_string(address_listen, ec);
boost::asio::ip::address mcast_addr = boost::asio::ip::address::from_string(address_mcast, ec);
if (strcmp(network_type, "ipv4") == 0)
{
listen_endpoint_ = udp::endpoint(listen_addr, port_num);
send_endpoint_ = udp::endpoint(mcast_addr, port_num);
}
else if (strcmp(network_type, "ipv6") == 0)
{
}
else
return ES35_INVALID_SOCKET_CONNECTION;
send_udp_socket_.reset(new boost::asio::ip::udp::socket(send_ios_));
receive_udp_socket_.reset(new boost::asio::ip::udp::socket(receive_ios_));
send_udp_socket_->open(boost::asio::ip::udp::v4());
receive_udp_socket_->open(listen_endpoint_.protocol());
send_udp_socket_->set_option(boost::asio::ip::udp::socket::reuse_address(true));
receive_udp_socket_->set_option(boost::asio::ip::udp::socket::reuse_address(true));
boost::asio::ip::address_v4 local_interface =
boost::asio::ip::address_v4::from_string(address_listen);
boost::asio::ip::multicast::outbound_interface option(local_interface);
// Join the multicast group.
receive_udp_socket_->set_option(
boost::asio::ip::multicast::join_group(mcast_addr));
send_udp_socket_->set_option(option);
receive_udp_socket_->set_option(option);
boost::asio::ip::multicast::hops hops_option(3);
//send_udp_socket_->set_option(hops_option);
receive_udp_socket_->set_option(hops_option);
receive_udp_socket_->bind(listen_endpoint_);
startReceive();
receive_thread_ = boost::thread(boost::bind(&boost::asio::io_service::run, &receive_ios_));
send_thread_ = boost::thread(boost::bind(&boost::asio::io_service::run, &send_ios_));
return ES_NoError;
}
catch (std::exception& exp)
{
std::string str = exp.what();
return ES35_INVALID_SOCKET_CONNECTION;
}
}
/***************************************************************************//**
\brief
\details
*******************************************************************************/
ARLErrorCode_e UDPClient::close(void)
{
try {
boost::system::error_code ec;
//udp_socket_->cancel();
//udp_socket_.shutdown(socket_base::shutdown_both, ec);
if (ec)
{
return ES35_INVALID_SOCKET_CONNECTION;
}
if (send_udp_socket_->is_open())
{
send_udp_socket_->close();
}
if (receive_udp_socket_->is_open())
{
receive_udp_socket_->close();
}
receive_udp_socket_.reset();
send_udp_socket_.reset();
}
catch (std::exception& e)
{
std::string str = e.what();
return ES35_INVALID_SOCKET_CONNECTION;
}
return ES_NoError;
}
/***************************************************************************//**
\brief
\details
*******************************************************************************/
bool UDPClient::isOpen()
{
return send_udp_socket_->is_open() && receive_udp_socket_->is_open();
}
/***************************************************************************//**
\brief Send a message.
\details The message is sent asynchronously.
\param message
\param message size
*******************************************************************************/
ARLErrorCode_e UDPClient::send(u8* message, u32 size)
{
if (!isOpen()) {
return ES35_INVALID_SOCKET_CONNECTION;
}
std::string send_to_address = send_endpoint_.address().to_string();
send_udp_socket_->set_option(asio::ip::multicast::enable_loopback(false));
send_udp_socket_->async_send_to(
buffer(message, size),
send_endpoint_,
bind(
&UDPClient::handleSend,
this,
asio::placeholders::error,
asio::placeholders::bytes_transferred));
return ES_NoError;
}
/***************************************************************************//**
\brief Do nothing.
\details This function has the required signature to be used as an
asynchronous send completion handler.
\param not used
\param not used
*******************************************************************************/
void UDPClient::handleSend(const system::error_code& error, size_t)
{
if (error)
{
BoostSocketClient::onError(ES35_UDP_SEND_ERROR, (u8*)error.message().c_str(), error.message().size());
}
else
{
send_udp_socket_->set_option(asio::ip::multicast::enable_loopback(true));
}
}
/***************************************************************************//**
\brief Start an asynchronous receiver.
*******************************************************************************/
void NetLib::UDPClient::startReceive()
{
receive_udp_socket_->async_receive_from(
buffer(_buffer),
sender_endpoint_,
bind(
&UDPClient::handleReceive,
this,
asio::placeholders::error,
asio::placeholders::bytes_transferred));
std::string sender_address = sender_endpoint_.address().to_string();
}
/***************************************************************************//**
\brief Pass received data to the base class.
\details A new receiver is started.
\param error code
\param data size
*******************************************************************************/
void NetLib::UDPClient::handleReceive(const system::error_code& error, size_t size)
{
if (!error || error == error::message_size)
{
BoostSocketClient::onReceived(_buffer.data(), size);
startReceive();
}
else
{
BoostSocketClient::onError(ES35_UDP_RECEIVE_ERROR, (u8*)error.message().c_str(), error.message().size());
}
}
This code is not receiving response.please check
http://permalink.gmane.org/gmane.comp.lib.boost.asio.user/2724 is invalid.
The following code seems to be invalid:
boost::asio::ip::address_v4 local_interface =
boost::asio::ip::address_v4::from_string(ip);
boost::asio::ip::multicast::outbound_interface option(local_interface);
sock.set_option(option);
With the following class
the header:
namespace msgSrv {
class endPoint {
public:
asio::ip::udp::endpoint ep;
endPoint(std::string ip, int port);
};
class msgSrv {
private:
asio::ip::udp::socket *asioSocket;
asio::io_service *asioIoService;
int listenPort;
boost::array<char, 1> rcvBuff;
asio::ip::udp::endpoint lastRcvdPcktEndp;
char * sbuff;
public:
boost::condition_variable cond;
boost::mutex mut;
msgSrv(int listenPort);
virtual ~msgSrv();
void start();
void pckRcvd(const asio::error_code& error, std::size_t bytes_transferred);
void sendTo(const char* buff, int len, endPoint ep);
void sendHnd(const asio::error_code& error, std::size_t bytes_transferred);
};
}
the .cpp
#include "msgSrv.h"
namespace msgSrv {
endPoint::endPoint(const std::string ip, int port) {
asio::ip::address addr = asio::ip::address::from_string(ip);
ep = asio::ip::udp::endpoint(addr, port);
}
msgSrv::msgSrv(int listenPort) {
// TODO Auto-generated constructor stub
this->listenPort = listenPort;
try {
asioIoService = new asio::io_service();
asioSocket = new asio::ip::udp::socket(*asioIoService,
asio::ip::udp::endpoint(asio::ip::udp::v4(), listenPort)); //new asio::ip::udp::socket_(*asioIoService, udp::endpoint(udp::v4(), listenPort));
} catch (std::exception &e) {
std::cerr << "Error initializing ioservice or socket:" << e.what();
}
asioIoService->run();
}
msgSrv::~msgSrv() {
// TODO Auto-generated destructor stub
delete asioIoService;
delete asioSocket;
}
void msgSrv::start() {
asioSocket->async_receive_from(asio::buffer(rcvBuff), lastRcvdPcktEndp,
boost::bind(&msgSrv::pckRcvd, this, asio::placeholders::error,
asio::placeholders::bytes_transferred));
}
void msgSrv::pckRcvd(const asio::error_code& error,
std::size_t bytes_transferred) {
std::cout << "Rcvd! " << lastRcvdPcktEndp.address().to_string() << ":"
<< lastRcvdPcktEndp.port() << "\n";
}
void msgSrv::sendTo(const char* buff, int len, endPoint ep) {
sbuff = new char[len];
mempcpy(sbuff, buff, len);
asioSocket->async_send_to(asio::buffer(sbuff, len), ep.ep, boost::bind(
&msgSrv::sendHnd, this, asio::placeholders::error,
asio::placeholders::bytes_transferred));
}
void msgSrv::sendHnd(const asio::error_code& error,
std::size_t bytes_transferred) {
std::cout << "Snt!\n";
delete sbuff;
}
}
and the following "main" file:
int main()
{
msgSrv::msgSrv aa(4450);
aa.start();
msgSrv::endPoint ep("127.0.0.1", 4450);
std::string a("Prova!");
int len = a.length();
aa.sendTo(a.c_str(), len, ep);
std::cout << "sent...\n";
std::cout << "notified...\n";
}
all I get is:
terminate called after throwing an instance of 'asio::system_error'
what(): mutex: Invalid argument
sent...
notified...
What's wrong?? I tried even to put a while(1) in the main, to see if something happens... I even tried to put a condition in the main that is unlocked by the receive handler... all remains locked... So what??? No idea!
I don't see you actually locking any muxtex, so that error is strange.
However your problem is to calling asioIoService->run() inside the constructor, witch fall in infinite loop. The solution is to create a new boost::thread, witch call asioIoService->run() itself. This thread will be processing all jobs. You may also call asio::io_service::run() with more then one thread, to get processing on more then one job at the same time.
m_thread = new boost::thread(boost::bind(&asio::io_service::run,asioIoService));
Calling asioIoService->stop() will force exit of asio::io_service::run(), thus closing the thread. You must join this thread to ensure that thread terminates before destroying asioIoService pointer in the destructor of your msgSrv class.