I'm doing some small project, trying to setup RAW socket, proof of concept program just to get socket up and running:
io.hpp:
#pragma once
#include <cstdlib>
#include <iostream>
#include <boost/asio.hpp>
#include <net/ethernet.h>
#include <sys/socket.h>
#include <linux/if_packet.h>
// change max length to bytes received
template <typename Protocol>
class ll_endpoint
{
private:
sockaddr_ll sockaddr;
public:
typedef Protocol protocol_type;
typedef boost::asio::detail::socket_addr_type data_type;
ll_endpoint(char* ifname)
{
sockaddr.sll_family = PF_PACKET;
sockaddr.sll_protocol = htons(ETH_P_ALL);
sockaddr.sll_ifindex = if_nametoindex(ifname);
sockaddr.sll_hatype = 1;
}
ll_endpoint& operator=(const ll_endpoint& other)
{
sockaddr = other.sockaddr;
return *this;
}
protocol_type protocol() const
{
return protocol_type();
}
data_type* data()
{
return (struct sockaddr*)&sockaddr;
}
const data_type* data() const
{
return (struct sockaddr*)&sockaddr;
}
std::size_t size() const
{
return sizeof(sockaddr);
}
void resize(std::size_t size)
{
/* nothing we can do here */
}
std::size_t capacity() const
{
return sizeof(sockaddr);
}
friend bool operator==(const ll_endpoint<Protocol>& e1,
const ll_endpoint<Protocol>& e2)
{
return ( e1.sockaddr.sll_addr == e2.sockaddr.sll_addr );
}
friend bool operator!=(const ll_endpoint<Protocol>& e1,
const ll_endpoint<Protocol>& e2)
{
return !(e1.sockaddr.sll_addr == e2.sockaddr.sll_addr);
}
friend bool operator<(const ll_endpoint<Protocol>& e1,
const ll_endpoint<Protocol>& e2)
{
return e1.sockaddr.sll_addr < e2.sockaddr.sll_addr;
}
friend bool operator>(const ll_endpoint<Protocol>& e1,
const ll_endpoint<Protocol>& e2)
{
return e2.sockaddr.sll_addr < e1.sockaddr.sll_addr;
}
friend bool operator<=(const ll_endpoint<Protocol>& e1,
const ll_endpoint<Protocol>& e2)
{
return !(e2 < e1);
}
friend bool operator>=(const ll_endpoint<Protocol>& e1,
const ll_endpoint<Protocol>& e2)
{
return !(e1 < e2);
}
};
class ll_protocol
{
public:
typedef boost::asio::basic_raw_socket<ll_protocol> socket;
typedef ll_endpoint<ll_protocol> endpoint;
int type() const {
return SOCK_RAW;
}
int protocol() const {
return protocol_;
}
int family() const{
return family_;
}
ll_protocol(int protocol, int family): protocol_(protocol), family_(family) {}
ll_protocol(): protocol_(htons(ETH_P_ALL)), family_(PF_PACKET) {}
private:
int protocol_;
int family_;
};
class IO {
typedef boost::asio::basic_raw_socket<ll_protocol> socket;
typedef ll_endpoint<ll_protocol> endpoint;
public:
IO(boost::asio::io_context& io_context, char* ifname)
: socket_(io_context, ll_endpoint<ll_protocol>(ifname))
{
do_receive();
}
void do_receive()
{
socket_.async_receive_from(
boost::asio::buffer(data_, max_length), sender_endpoint_,
[this](boost::system::error_code ec, std::size_t bytes_recvd) // <--- handler
{
if (!ec && bytes_recvd > 0)
{
do_send(bytes_recvd);
}
else
{
do_receive();
}
});
}
void do_send(std::size_t length)
{
socket_.async_send_to(
boost::asio::buffer(data_, length), sender_endpoint_,
[this](boost::system::error_code /*ec*/, std::size_t /*bytes_sent*/)
{
do_receive();
});
}
private:
socket socket_;
endpoint sender_endpoint_;
enum { max_length = 9000 };
char data_[max_length]; // change to vector?, const std::vector<uint8_t>& packet
};
Main.cpp
#include <cctype>
#include <cstdlib>
#include <iostream>
#include <boost/asio.hpp>
#include "io.hpp"
int main(int argc, char *argv[]) {
try
{
boost::asio::io_context io_context;
IO s(io_context, argv[1]);
io_context.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
On compile I get this error:
io.hpp:142:56: error: expected primary-expression before ‘>’ token
142 | : socket_(io_context, sender_endpoint_<ll_protocol>(ifname))
| ^
io.hpp:142:65: error: no matching function for call to ‘ll_endpoint<ll_protocol>::ll_endpoint()’
142 | : socket_(io_context, sender_endpoint_<ll_protocol>(ifname))
| ^
io.hpp:25:5: note: candidate: ‘ll_endpoint<Protocol>::ll_endpoint(const char*) [with Protocol = ll_protocol]’
25 | ll_endpoint(const char* ifname)
| ^~~~~~~~~~~
io.hpp:25:5: note: candidate expects 1 argument, 0 provided <--- but why *thanks Useless for spotting this, however even I pass something error is the same
Is it a template misuse and what I am missing?
expectation is to setup socket in a similar way to https://www.boost.org/doc/libs/1_70_0/doc/html/boost_asio/example/cpp11/echo/async_udp_echo_server.cpp
The member variable sender_endpoint_ have the type endpoint, which is an alias of ll_endpoint<ll_protocol>.
The type endpoint is a concrete type, it's not a template. So adding template-arguments for sender_endpoint_ makes no sense.
Just remove the template-arguments: sender_endpoint_(ifname)
Related
I am unable to receive data over serial port in boost::asio while using asynchronous. When I use synchronous routines I am able to receive data.
Code :
SerialPort.cpp
bool SerialPort::read_async(std::uint32_t read_timeout)
{
try
{
if (read_timeout not_eq SerialPort::ignore_timeout)
this->read_timeout = read_timeout;//If read_timeout is not set to ignore_timeout, update the read_timeout else use old read_timeout
this->port.async_read_some(boost::asio::buffer(this->read_buffer.data(), this->read_buffer.size()),
boost::bind(&SerialPort::read_handler, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
return true;
}
catch (const std::exception& ex)
{
PLOG_ERROR << ex.what();
return false;
}
}
void SerialPort::read_handler(const boost::system::error_code& error, std::size_t bytes_transferred)
{
std::string received_data_buffer;
std::transform(this->read_buffer.begin(), this->read_buffer.begin() + bytes_transferred,
std::back_inserter(received_data_buffer), [](std::byte character) {
return static_cast<char>(character);
});
PLOG_INFO << "In Read Buffer : " << received_data_buffer;
}
bool SerialPort::open_port(void)
{
try
{
this->port.open(this->port_name);
return true;
}
catch (const std::exception& ex)
{
PLOG_FATAL << ex.what();
}
return false;
}
SerialPort.hpp
class SerialPort
{
private:
boost::asio::io_context io;
boost::asio::serial_port port;
boost::asio::serial_port::native_handle_type native_port;
std::string port_name;
const static std::uint32_t READ_BUFFER_MAX_LENGTH{ 8096 };
std::array<std::byte, SerialPort::READ_BUFFER_MAX_LENGTH> read_buffer;//Used in synchronous read
void read_handler(
const boost::system::error_code& error, // Result of operation.
std::size_t bytes_transferred // Number of bytes read.
);
//boost::asio::deadline_timer timer;
public:
SerialPort() : io(), port(io), thread_sync_read()
{
}
~SerialPort();
bool open_port(void);
bool read_async(std::uint32_t read_timeout = SerialPort::ignore_timeout);
};
main.cpp
SerialPort sp;
int main()
{
sp.open_port("COM11");
sp.write_sync("Testing123");
sp.read_async();
while (true)
{
}
return 0;
}
You're supposedly trying to do some operations asynchronously.
Firstly, mixing synchronous and asynchronous operations is not always advisable. Some services/IO objects might hold inner state that assumes one or the other.
Secondly, the asynchronous operation requires the io_service to be run. That doesn't happen. You could make it explicit instead of the current while() loop.
#include <boost/asio.hpp>
#include <boost/bind/bind.hpp>
#include <iostream>
namespace asio = boost::asio;
using boost::system::error_code;
std::ostream PLOG_INFO(std::clog.rdbuf());
std::ostream PLOG_ERROR(std::clog.rdbuf());
std::ostream PLOG_FATAL(std::clog.rdbuf());
class SerialPort
{
public:
SerialPort()
: io_()
, port_(io_) /*, thread_sync_read()*/
{}
~SerialPort() = default;
bool open_port(std::string name);
static constexpr uint32_t ignore_timeout = -1;
bool read_async(std::uint32_t read_timeout = SerialPort::ignore_timeout);
void run() { io_.run(); }
private:
static constexpr uint32_t READ_BUFFER_MAX_LENGTH{8096};
asio::io_context io_;
asio::serial_port port_;
std::string port_name_;
uint32_t read_timeout_ = ignore_timeout;
// asio::deadline_timer timer;
std::array<std::byte, READ_BUFFER_MAX_LENGTH> read_buffer_;
void read_handler(error_code error, size_t bytes_transferred);
};
bool SerialPort::read_async(std::uint32_t read_timeout) {
try {
if (read_timeout != SerialPort::ignore_timeout)
read_timeout_ =
read_timeout; // If read_timeout is not set to ignore_timeout,
// update the read_timeout else use old
// read_timeout
port_.async_read_some(
asio::buffer(read_buffer_.data(), read_buffer_.size()),
boost::bind(&SerialPort::read_handler, this,
asio::placeholders::error,
asio::placeholders::bytes_transferred));
return true;
} catch (const std::exception& ex) {
PLOG_ERROR << ex.what() << std::endl;
return false;
}
}
void SerialPort::read_handler(error_code error, size_t bytes_transferred) {
std::string s;
std::transform(
read_buffer_.begin(), read_buffer_.begin() + bytes_transferred,
std::back_inserter(s),
[](std::byte character) { return static_cast<char>(character); });
PLOG_INFO << "In Read Buffer : " << s << " (" << error.message() << ")" << std::endl;
}
bool SerialPort::open_port(std::string name) {
try {
port_name_ = std::move(name);
port_.open(port_name_);
return true;
} catch (std::exception const& ex) {
PLOG_FATAL << ex.what() << std::endl;
return false;
}
}
SerialPort sp;
int main(int argc, char** argv) {
sp.open_port(argc > 1 ? argv[1] : "COM11");
// sp.write_sync("Testing123");
sp.read_async();
sp.run();
}
My goal is to be able to do load multiple PEM files in boost so it can do a correct SSL handshake with a client depending on the SNI sent in TLS which means a single ip address can host multiple https sites. I am looking through the boost document SSL section, but nothing there about SNI. Because my project depends on boost::asio and I don’t know if it is possible and how to refer the example code, s_server.c, in openssl directory ( https://github.com/openssl/openssl/blob/master/apps/s_server.c#L127 ) THANK YOU for any hints.
And my https server code is here:
template<class service_pool_policy = io_service_pool>
class httpTLS_server_ : private noncopyable {
public:
template<class... Args>
explicit httpTLS_server_(Args&&... args) : io_service_pool_(std::forward<Args>(args)...)
, ctx_(boost::asio::ssl::context::tls_server)
{
http_cache::get().set_cache_max_age(86400);
init_conn_callback();
}
void enable_http_cache(bool b) {
http_cache::get().enable_cache(b);
}
template<typename F>
void init_ssl_context(bool ssl_enable_v3, F&& f, std::string certificate_chain_file,
std::string private_key_file, std::string tmp_dh_file) {
unsigned long ssl_options = boost::asio::ssl::context::default_workarounds
| boost::asio::ssl::context::no_sslv2
| boost::asio::ssl::context::no_sslv3
| boost::asio::ssl::context::no_tlsv1
| boost::asio::ssl::context::single_dh_use;
ctx_.set_options(ssl_options);
ctx_.set_password_callback(std::forward<F>(f));
ctx_.use_certificate_chain_file(std::move(certificate_chain_file));
ctx_.use_private_key_file(std::move(private_key_file), boost::asio::ssl::context::pem);
}
//address :
// "0.0.0.0" : ipv4. use 'https://localhost/' to visit
// "::1" : ipv6. use 'https://[::1]/' to visit
// "" : ipv4 & ipv6.
bool listen(std::string_view address, std::string_view port) {
boost::asio::ip::tcp::resolver::query query(address.data(), port.data());
return listen(query);
}
//support ipv6 & ipv4
bool listen(std::string_view port) {
boost::asio::ip::tcp::resolver::query query(port.data());
return listen(query);
}
bool listen(const boost::asio::ip::tcp::resolver::query & query) {
boost::asio::ip::tcp::resolver resolver(io_service_pool_.get_io_service());
boost::asio::ip::tcp::resolver::iterator endpoints = resolver.resolve(query);
bool r = false;
for (; endpoints != boost::asio::ip::tcp::resolver::iterator(); ++endpoints) {
boost::asio::ip::tcp::endpoint endpoint = *endpoints;
auto acceptor = std::make_shared<boost::asio::ip::tcp::acceptor>(io_service_pool_.get_io_service());
acceptor->open(endpoint.protocol());
acceptor->set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
try {
acceptor->bind(endpoint);
acceptor->listen();
start_accept(acceptor);
r = true;
}
catch (const std::exception& ex) {
std::cout << ex.what() << "\n";
//LOG_INFO << e.what();
}
}
return r;
}
void stop() {
io_service_pool_.stop();
}
void run() {
if (!fs::exists(public_root_path_.data())) {
fs::create_directories(public_root_path_.data());
}
if (!fs::exists(static_dir_.data())) {
fs::create_directories(static_dir_.data());
}
io_service_pool_.run();
}
intptr_t run_one() {
return io_service_pool_.run_one();
}
intptr_t poll() {
return io_service_pool_.poll();
}
intptr_t poll_one() {
return io_service_pool_.poll_one();
}
void set_static_dir(std::string&& path) {
static_dir_ = public_root_path_+std::move(path)+"/";
}
const std::string& static_dir() const {
return static_dir_;
}
//xM
void set_max_req_buf_size(std::size_t max_buf_size) {
max_req_buf_size_ = max_buf_size;
}
void set_keep_alive_timeout(long seconds) {
keep_alive_timeout_ = seconds;
}
template<typename T>
bool need_cache(T&& t) {
if constexpr(std::is_same_v<T, enable_cache<bool>>) {
return t.value;
}
else {
return false;
}
}
//set http handlers
template<http_method... Is, typename Function, typename... AP>
void set_http_handler(std::string_view name, Function&& f, AP&&... ap) {
if constexpr(has_type<enable_cache<bool>, std::tuple<std::decay_t<AP>...>>::value) {//for cache
bool b = false;
((!b&&(b = need_cache(std::forward<AP>(ap)))),...);
if (!b) {
http_cache::get().add_skip(name);
}else{
http_cache::get().add_single_cache(name);
}
auto tp = filter<enable_cache<bool>>(std::forward<AP>(ap)...);
auto lm = [this, name, f = std::move(f)](auto... ap) {
https_router_.register_handler<Is...>(name, std::move(f), std::move(ap)...);
};
std::apply(lm, std::move(tp));
}
else {
https_router_.register_handler<Is...>(name, std::forward<Function>(f), std::forward<AP>(ap)...);
}
}
void set_base_path(const std::string& key,const std::string& path)
{
base_path_[0] = std::move(key);
base_path_[1] = std::move(path);
}
void set_res_cache_max_age(std::time_t seconds)
{
static_res_cache_max_age_ = seconds;
}
std::time_t get_res_cache_max_age()
{
return static_res_cache_max_age_;
}
void set_cache_max_age(std::time_t seconds)
{
http_cache::get().set_cache_max_age(seconds);
}
std::time_t get_cache_max_age()
{
return http_cache::get().get_cache_max_age();
}
//don't begin with "./" or "/", not absolutely path
void set_public_root_directory(const std::string& name)
{
if(!name.empty()){
public_root_path_ = "./"+name+"/";
}
else {
public_root_path_ = "./";
}
}
std::string get_public_root_directory()
{
return public_root_path_;
}
void set_download_check(std::function<bool(request_ssl& req, response& res)> checker) {
download_check_ = std::move(checker);
}
//should be called before listen
void set_upload_check(std::function<bool(request_ssl& req, response& res)> checker) {
upload_check_ = std::move(checker);
}
void mapping_to_root_path(std::string relate_path) {
relate_paths_.emplace_back("."+std::move(relate_path));
}
private:
void start_accept(std::shared_ptr<boost::asio::ip::tcp::acceptor> const& acceptor) {
auto new_conn = std::make_shared<connection_ssl<Socket_ssl>>(
io_service_pool_.get_io_service(), max_req_buf_size_, keep_alive_timeout_, https_handler_, static_dir_,
upload_check_?&upload_check_ : nullptr
, ctx_
);
acceptor->async_accept(new_conn->socket(), [this, new_conn, acceptor](const boost::system::error_code& e) {
if (!e) {
new_conn->socket().set_option(boost::asio::ip::tcp::no_delay(true));
new_conn->start();
}
else {
std::cout << "server::handle_accept: " << e.message();
//LOG_INFO << "server::handle_accept: " << e.message();
}
start_accept(acceptor);
});
}
void init_conn_callback() {
set_static_res_handler();
https_handler_ = [this](request_ssl& req, response& res) {
res.set_base_path(this->base_path_[0],this->base_path_[1]);
res.set_url(req.get_url());
try {
bool success = https_router_.route(req.get_method(), req.get_url(), req, res);
if (!success) {
//updated by neo
//res.set_status_and_content(status_type::bad_request, "the url is not right");
res.redirect("/");
//updated end
}
}
catch (const std::exception& ex) {
res.set_status_and_content(status_type::internal_server_error, ex.what()+std::string(" exception in business function"));
}
catch (...) {
res.set_status_and_content(status_type::internal_server_error, "unknown exception in business function");
}
};
}
service_pool_policy io_service_pool_;
std::size_t max_req_buf_size_ = 3 * 1024 * 1024; //max request buffer size 3M
long keep_alive_timeout_ = 60; //max request timeout 60s
https_router https_router_;
std::string static_dir_ = "./public/static/"; //default
std::string base_path_[2] = {"base_path","/"};
std::time_t static_res_cache_max_age_ = 0;
std::string public_root_path_ = "./";
boost::asio::ssl::context ctx_;
//SSL_CTX ctx_ ;
//SSL_CTX *ctx2 = NULL;
https_handler https_handler_ = nullptr;
std::function<bool(request_ssl& req, response& res)> download_check_;
std::vector<std::string> relate_paths_;
std::function<bool(request_ssl& req, response& res)> upload_check_ = nullptr;
};
using httpTLS_server = httpTLS_server_<io_service_pool>;
I'm trying to hack into an existing appilication a socks4 client. The program uses asynchronous boost::asio.
So i've worked out so far that i need to negotiate with the socks4 server first:
boost::asio::ip::tcp::endpoint socks_proxy{boost::asio::ip::make_address("127.0.0.1"),1080};
if( socks_proxy.protocol() != boost::asio::ip::tcp::v4() )
{
throw boost::system::system_error(
boost::asio::error::address_family_not_supported);
}
....
boost::asio::ip::tcp::socket* m_socket;
// negotiate with the socks server
// m_endpoint is an item in std::queue<boost::asio::ip::basic_endpoint<boost::asio::ip::tcp>> m_endpoints
boost::asio::ip::address_v4::bytes_type address_ = m_endpoint.address().to_v4().to_bytes();
unsigned short port = m_endpoint.port();
unsigned char port_high_byte_ = (port >> 8) & 0xff;
unsigned char port_low_byte_ = port & 0xff;
boost::array<boost::asio::const_buffer, 7> send_buffer =
{
{
boost::asio::buffer(&SOCKS_VERSION, 1), // const unsigned char SOCKS_VERSION = 0x04;
boost::asio::buffer(&SOCKS_CONNECT, 1), // const unsigned char SOCKS_VERSION = 0x04;
boost::asio::buffer(&port_high_byte_, 1),
boost::asio::buffer(&port_low_byte_, 1),
boost::asio::buffer(address_),
boost::asio::buffer("userid"),
boost::asio::buffer(&null_byte_, 1). // unsigned char null_byte_ = 0;
}
};
// initiate socks
boost::asio::write( m_socket, send_buffer );
// check it worked
unsigned char status_;
boost::array<boost::asio::mutable_buffer, 5> reply_buffer =
{
{
boost::asio::buffer(&null_byte_, 1),
boost::asio::buffer(&status_, 1),
boost::asio::buffer(&port_high_byte_, 1),
boost::asio::buffer(&port_low_byte_, 1),
boost::asio::buffer(address_)
}
};
boost::asio::read( m_socket, reply_buffer );
if( ! ( null_byte_ == 0 && status_ == 0x5a ) )
{
std::cout << "Proxy connection failed.\n";
}
However, the exist application code bascially does:
boost::asio::ip::tcp::socket* m_socket;
m_nonsecuresocket = std::make_shared<boost::asio::ip::tcp::socket>(m_io_service);
m_socket = m_nonsecuresocket.get();
m_socket->async_connect(m_endpoint,
m_io_strand.wrap(boost::bind(&CLASS::connect_handler, this, _1)));
so that even if i could get it to compile, the async_connect would disconnect the socket anyway.
How can i integrate the socks4 client code into the async_connect()?
As I commented, I think your question requires a lot more focus. However, since this is actually a useful question and it might be good to have an example, I went ahead and implemented a socks4::async_proxy_connect operation:
tcp::socket sock{io};
tcp::endpoint
target({}, 80), // connect to localhost:http
proxy{{}, 1080}; // via SOCKS4 proxy on localhost:1080
socks4::async_proxy_connect(sock, target, proxy, handler);
// continue using sock
Loose ends:
synchronous version is not implemented yet (but should be a lot simpler) added
does not include address resolution (just as your question). Integrating that would require quite a bit of the groundwork in boost::asio::async_connect that takes a resolver query. Sadly, that doesn't seen well factored for reuse.
Listing
File socks4.hpp
#include <boost/asio.hpp>
#include <boost/endian/arithmetic.hpp>
namespace socks4 { // threw in the kitchen sink for error codes
#ifdef STANDALONE_ASIO
using std::error_category;
using std::error_code;
using std::error_condition;
using std::system_error;
#else
namespace asio = boost::asio;
using boost::system::error_category;
using boost::system::error_code;
using boost::system::error_condition;
using boost::system::system_error;
#endif
enum class result_code {
ok = 0,
invalid_version = 1,
rejected_or_failed = 3,
need_identd = 4,
unconirmed_userid = 5,
//
failed = 99,
};
auto const& get_result_category() {
struct impl : error_category {
const char* name() const noexcept override { return "result_code"; }
std::string message(int ev) const override {
switch (static_cast<result_code>(ev)) {
case result_code::ok: return "Success";
case result_code::invalid_version: return "SOCKS4 invalid reply version";
case result_code::rejected_or_failed: return "SOCKS4 rejected or failed";
case result_code::need_identd: return "SOCKS4 unreachable (client not running identd)";
case result_code::unconirmed_userid: return "SOCKS4 identd could not confirm user ID";
case result_code::failed: return "SOCKS4 general unexpected failure";
default: return "unknown error";
}
}
error_condition
default_error_condition(int ev) const noexcept override {
return error_condition{ev, *this};
}
bool equivalent(int ev, error_condition const& condition)
const noexcept override {
return condition.value() == ev && &condition.category() == this;
}
bool equivalent(error_code const& error,
int ev) const noexcept override {
return error.value() == ev && &error.category() == this;
}
} const static instance;
return instance;
}
error_code make_error_code(result_code se) {
return error_code{
static_cast<std::underlying_type<result_code>::type>(se),
get_result_category()};
}
} // namespace socks4
template <>
struct boost::system::is_error_code_enum<socks4::result_code>
: std::true_type {};
namespace socks4 {
using namespace std::placeholders;
template <typename Endpoint> struct core_t {
Endpoint _target;
Endpoint _proxy;
core_t(Endpoint target, Endpoint proxy)
: _target(target)
, _proxy(proxy) {}
#pragma pack(push)
#pragma pack(1)
using ipv4_octets = boost::asio::ip::address_v4::bytes_type;
using net_short = boost::endian::big_uint16_t;
struct alignas(void*) Req {
uint8_t version = 0x04;
uint8_t cmd = 0x01;
net_short port;
ipv4_octets address;
} _request{0x04, 0x01, _target.port(),
_target.address().to_v4().to_bytes()};
struct alignas(void*) Res {
uint8_t reply_version;
uint8_t status;
net_short port;
ipv4_octets address;
} _response;
#pragma pack(pop)
using const_buffer = boost::asio::const_buffer;
using mutable_buffer = boost::asio::mutable_buffer;
auto request_buffers(char const* szUserId) const {
return std::array<const_buffer, 2>{
boost::asio::buffer(&_request, sizeof(_request)),
boost::asio::buffer(szUserId, strlen(szUserId) + 1)};
}
auto response_buffers() {
return boost::asio::buffer(&_response, sizeof(_response));
}
error_code get_result(error_code ec = {}) const {
if (ec)
return ec;
if (_response.reply_version != 0)
return result_code::invalid_version;
switch (_response.status) {
case 0x5a: return result_code::ok; // Request grantd
case 0x5B: return result_code::rejected_or_failed;
case 0x5C: return result_code::need_identd;
case 0x5D: return result_code::unconirmed_userid;
}
return result_code::failed;
}
};
template <typename Socket, typename Completion>
struct async_proxy_connect_op {
using Endpoint = typename Socket::protocol_type::endpoint;
using executor_type = typename Socket::executor_type;
auto get_executor() { return _socket.get_executor(); }
private:
core_t<Endpoint> _core;
Socket& _socket;
std::string _userId;
Completion _handler;
public:
async_proxy_connect_op(Completion handler, Socket& s, Endpoint target,
Endpoint proxy, std::string user_id = {})
: _core(target, proxy)
, _socket(s)
, _userId(std::move(user_id))
, _handler(std::move(handler)) {}
using Self = std::unique_ptr<async_proxy_connect_op>;
void init(Self&& self) { operator()(self, INIT{}); }
private:
// states
struct INIT{};
struct CONNECT{};
struct SENT{};
struct ONRESPONSE{};
struct Binder {
Self _self;
template <typename... Args>
decltype(auto) operator()(Args&&... args) {
return (*_self)(_self, std::forward<Args>(args)...);
}
};
void operator()(Self& self, INIT) {
_socket.async_connect(_core._proxy,
std::bind(Binder{std::move(self)}, CONNECT{}, _1));
}
void operator()(Self& self, CONNECT, error_code ec) {
if (ec) return _handler(ec);
boost::asio::async_write(
_socket,
_core.request_buffers(_userId.c_str()),
std::bind(Binder{std::move(self)}, SENT{}, _1, _2));
}
void operator()(Self& self, SENT, error_code ec, size_t xfer) {
if (ec) return _handler(ec);
auto buf = _core.response_buffers();
boost::asio::async_read(
_socket, buf, boost::asio::transfer_exactly(buffer_size(buf)),
std::bind(Binder{std::move(self)}, ONRESPONSE{}, _1, _2));
}
void operator()(Self& self, ONRESPONSE, error_code ec, size_t xfer) {
_handler(_core.get_result(ec));
}
};
template <typename Socket,
typename Endpoint = typename Socket::protocol_type::endpoint>
error_code proxy_connect(Socket& s, Endpoint ep, Endpoint proxy,
std::string const& user_id, error_code& ec) {
core_t<Endpoint> core(ep, proxy);
ec.clear();
s.connect(core._proxy, ec);
if (!ec)
boost::asio::write(s, core.request_buffers(user_id.c_str()),
ec);
auto buf = core.response_buffers();
if (!ec)
boost::asio::read(s, core.response_buffers(),
boost::asio::transfer_exactly(buffer_size(buf)), ec);
return ec = core.get_result(ec);
}
template <typename Socket,
typename Endpoint = typename Socket::protocol_type::endpoint>
void proxy_connect(Socket& s, Endpoint ep, Endpoint proxy,
std::string const& user_id = "") {
error_code ec;
if (proxy_connect(s, ep, proxy, user_id, ec))
throw system_error(ec);
}
template <typename Socket, typename Token,
typename Endpoint = typename Socket::protocol_type::endpoint>
auto async_proxy_connect(Socket& s, Endpoint ep, Endpoint proxy,
std::string user_id, Token&& token) {
using Result = asio::async_result<std::decay_t<Token>, void(error_code)>;
using Completion = typename Result::completion_handler_type;
Completion completion(std::forward<Token>(token));
Result result(completion);
using Op = async_proxy_connect_op<Socket, Completion>;
// make an owning self ptr, to serve a unique async chain
auto self =
std::make_unique<Op>(completion, s, ep, proxy, std::move(user_id));
self->init(std::move(self));
return result.get();
}
template <typename Socket, typename Token,
typename Endpoint = typename Socket::protocol_type::endpoint>
auto async_proxy_connect(Socket& s, Endpoint ep, Endpoint proxy, Token&& token) {
return async_proxy_connect<Socket, Token, Endpoint>(
s, ep, proxy, "", std::forward<Token>(token));
}
} // namespace socks4
Demo
File test.cpp
#include "socks4.hpp"
#include <boost/beast.hpp>
#include <boost/beast/http.hpp>
#include <iostream>
int main(int argc, char**) {
bool synchronous = argc > 1;
using boost::asio::ip::tcp;
boost::asio::thread_pool ctx(1); // just one thread will do
tcp::socket sock{ctx};
tcp::endpoint target(
boost::asio::ip::address_v4::from_string("173.203.57.63"), 80),
proxy{{}, 1080};
try {
if (synchronous) {
std::cerr << "Using synchronous interface" << std::endl;
socks4::proxy_connect(sock, target,
proxy); // throws system_error if failed
} else {
std::cerr << "Using asynchronous interface" << std::endl;
// using the async interface (still emulating synchronous by using
// future for brevity of this demo)
auto fut = socks4::async_proxy_connect(sock, target, proxy,
boost::asio::use_future);
fut.get(); // throws system_error if failed
}
// Now do a request using beast
namespace beast = boost::beast;
namespace http = beast::http;
{
http::request<http::empty_body> req(http::verb::get, "/", 11);
req.set(http::field::host, "coliru.stacked-crooked.com");
req.set(http::field::connection, "close");
std::cout << "-------\nRequest: " << req << "\n-------\n";
http::write(sock, req);
}
{
http::response<http::string_body> res;
beast::flat_buffer buf;
http::read(sock, buf, res);
std::cout << "\n-------\nResponse: " << res << "\n";
}
} catch(socks4::system_error const& se) {
std::cerr << "Error: " << se.code().message() << std::endl;
}
ctx.join();
}
Output
Using asynchronous interface
-------
Request: GET / HTTP/1.1
Host: coliru.stacked-crooked.com
Connection: close
-------
-------
Response: HTTP/1.1 200 OK
Content-Type: text/html;charset=utf-8
Content-Length: 8616
Server: WEBrick/1.4.2 (Ruby/2.5.1/2018-03-29) OpenSSL/1.0.2g
Date: Thu, 29 Apr 2021 19:05:03 GMT
Connection: close
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
<html>
<head>
<title>Coliru</title>
(rest of response omitted)
I've put together an asynchronous UDP client (code below) which is throwing the error "vector iterator not dereferenceable" after about 4 seconds of running. There appears to be little information on the exact cause of this error within the boost asio context but it would seem that it may be due to a buffer going out of scope.
I'm using shared pointers and I can't see anywhere that would allow them to go out of scope.
Call stack:
std::_Debug_message(const wchar_t * message, const wchar_t * file, unsigned int line) Line 15 C++
std::_Vector_const_iterator<std::_Vector_val<std::_Simple_types<unsigned char> > >::operator*() Line 72 C++
std::_Vector_iterator<std::_Vector_val<std::_Simple_types<unsigned char> > >::operator*() Line 325 C++
boost::asio::detail::buffer_debug_check<std::_Vector_iterator<std::_Vector_val<std::_Simple_types<unsigned char> > > >::operator()() Line 532 C++
[External Code]
boost::asio::detail::buffer_cast_helper(const boost::asio::mutable_buffer & b) Line 146 C++
boost::asio::buffer_cast<void const *>(const boost::asio::mutable_buffer & b) Line 427 C++
boost::asio::detail::buffer_sequence_adapter<boost::asio::const_buffer,boost::asio::mutable_buffers_1>::validate(const boost::asio::mutable_buffers_1 & buffer_sequence) Line 207 C++
boost::asio::detail::win_iocp_socket_send_op<boost::asio::mutable_buffers_1,boost::_bi::bind_t<void,boost::_mfi::mf2<void,client_t,boost::system::error_code const &,unsigned int>,boost::_bi::list3<boost::_bi::value<client_t *>,boost::arg<1>,boost::arg<2> > > >::do_complete(boost::asio::detail::win_iocp_io_service * owner, boost::asio::detail::win_iocp_operation * base, const boost::system::error_code & result_ec, unsigned int bytes_transferred) Line 70 C++
boost::asio::detail::win_iocp_operation::complete(boost::asio::detail::win_iocp_io_service & owner, const boost::system::error_code & ec, unsigned int bytes_transferred) Line 46 C++
boost::asio::detail::win_iocp_io_service::do_one(bool block, boost::system::error_code & ec) Line 406 C++
boost::asio::detail::win_iocp_io_service::run(boost::system::error_code & ec) Line 164 C++
boost::asio::io_service::run() Line 59 C++
boost::_mfi::mf0<unsigned int,boost::asio::io_service>::operator()(boost::asio::io_service * p) Line 50 C++
boost::_bi::list1<boost::_bi::value<boost::asio::io_service *> >::operator()<unsigned int,boost::_mfi::mf0<unsigned int,boost::asio::io_service>,boost::_bi::list0>(boost::_bi::type<unsigned int> __formal, boost::_mfi::mf0<unsigned int,boost::asio::io_service> & f, boost::_bi::list0 & a, long __formal) Line 250 C++
boost::_bi::bind_t<unsigned int,boost::_mfi::mf0<unsigned int,boost::asio::io_service>,boost::_bi::list1<boost::_bi::value<boost::asio::io_service *> > >::operator()() Line 1223 C++
boost::detail::thread_data<boost::_bi::bind_t<unsigned int,boost::_mfi::mf0<unsigned int,boost::asio::io_service>,boost::_bi::list1<boost::_bi::value<boost::asio::io_service *> > > >::run() Line 117 C++
boost::`anonymous namespace'::thread_start_function(void * param) Line 303 C++
[External Code]
It looks like it's occurring during the 'do_send' function call 'async_send_to' but the shared pointer is stored in sended so I don't know how that could be getting deleted?
And the class:
#include <boost/asio.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>
#include <boost/thread.hpp>
#include <boost/lockfree/spsc_queue.hpp>
#include <boost/atomic.hpp>
#include <string>
#include <vector>
namespace asio = boost::asio;
namespace posix_time = boost::posix_time;
namespace lockfree = boost::lockfree;
typedef std::vector<uint8_t> msg_t;
typedef boost::shared_ptr<msg_t> spmsg_t;
typedef boost::shared_ptr<asio::io_service::work> pwork_t;
typedef lockfree::spsc_queue<spmsg_t> msg_queue_t;
struct client_t {
asio::io_service &_svc;
asio::ip::udp::socket _socket;
asio::ip::udp::endpoint _ep;
boost::atomic<bool> _connected;
boost::atomic<bool> _closing;
boost::thread _worker;
pwork_t _work;
msg_queue_t _send_queue;
msg_queue_t _recv_queue;
boost::condition_variable m_cond;
boost::mutex m_Mutex;
client_t(asio::io_service &svc_) :_svc(svc_), _send_queue(1024), _recv_queue(1024), _socket(svc_, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), 0)){}
void init(const std::string ip_, const std::string port_) {
_closing = false;
sending = false;
_connected = true;
boost::asio::ip::udp::resolver resolver(_svc);
_ep = *resolver.resolve({ boost::asio::ip::udp::v4(), ip_, port_ });
_work = pwork_t(new asio::io_service::work(_svc));
_worker.swap(boost::move(boost::thread(boost::bind(&asio::io_service::run, &_svc))));
_svc.post(boost::bind(&client_t::do_recv, this));
}
void destroy() {
_work.reset();
_worker.join();
_send_queue.reset();
_recv_queue.reset();
}
bool is_connected() {
return !_closing && _connected;
}
void disconnect() {
_closing = true;
}
boost::atomic<bool> sending;
spmsg_t sended;
void do_send() {
if (_send_queue.pop(sended)) {
_socket.async_send_to(asio::buffer(*sended), _ep, boost::bind(&client_t::handle_sended, this, asio::placeholders::error, asio::placeholders::bytes_transferred));
}
else {
sending.store(false);
if (_closing) {
_socket.shutdown(asio::ip::tcp::socket::shutdown_both);
_socket.close();
_send_queue.reset();
_recv_queue.reset();
_connected = false;
_closing = false;
}
}
}
void handle_sended(const boost::system::error_code &ec_, std::size_t bytes_transferred) {
if (ec_) {
sending.store(false);
printf("%s\n", ec_.message().c_str());
return;
}
do_send();
}
spmsg_t recv_msg;
void do_recv() {
recv_msg = boost::make_shared<msg_t>(2048);
_socket.async_receive_from(asio::buffer(recv_msg->data() + 4, 2044), _ep, boost::bind(&client_t::handle_recv, this, asio::placeholders::error, asio::placeholders::bytes_transferred));
}
void handle_recv(const boost::system::error_code &ec_, std::size_t bytes_transferred_) {
if (ec_) {
printf("%s\n",ec_.message().c_str());
return;
}
*(uint32_t*)recv_msg->data() = bytes_transferred_;
if (_recv_queue.push(recv_msg)) {
m_cond.notify_one();
do_recv();
}
else {
disconnect();
}
}
//spmsg_t get_sended(uint32_t len_) { return spmsg_t(new msg_t(len_ + 4)); }
bool send(unsigned char* msg, int msgSize) {
if (!is_connected())
return false;
spmsg_t msg_ = spmsg_t(new msg_t(msgSize));
memcpy(&msg_->data()[0], msg, msgSize);
if (!_send_queue.push(msg_))
return false;
//send_messages();
return true;
}
bool send_messages() {
if (!is_connected())
return false;
static bool f = false;
if (!sending.compare_exchange_strong(f, true))
return false;
_svc.post(boost::bind(&client_t::do_send, this));
}
spmsg_t recv() {
if (!is_connected())
return NULL;
spmsg_t recved;
if (_recv_queue.pop(recved))
return recved;
else{
boost::mutex::scoped_lock lock(m_Mutex);
if (m_cond.timed_wait(lock, boost::posix_time::milliseconds(5000)))
{
_recv_queue.pop(recved);
return recved;
}
else
return NULL;
}
}
};
It is fairly easy to create IP, TCP or UDP sockets using boost::asio library. But when it comes to Ethernet sockets for instance, you need to implement boost/asio/basic_raw_socket.hpp
As there are no examples of such a thing over the internet and as I spent a long time before finding the answer, I'll put my work-around in here.
The most helpful resource I found was: AF_NETLINK (netlink) sockets using boost::asio
A raw socket can be opened using the generic::raw_protocol stuff:
std::string ifname("eth1");
typedef boost::asio::generic::raw_protocol raw_protocol_t;
typedef boost::asio::generic::basic_endpoint<raw_protocol_t> raw_endpoint_t;
sockaddr_ll sockaddr;
memset(&sockaddr, 0, sizeof(sockaddr));
sockaddr.sll_family = PF_PACKET;
sockaddr.sll_protocol = htons(ETH_P_ALL);
sockaddr.sll_ifindex = if_nametoindex(ifname.c_str());
sockaddr.sll_hatype = 1;
raw_protocol_t::socket socket(io_service, raw_protocol_t(PF_PACKET, SOCK_RAW))
socket.bind(raw_endpoint_t(&sockaddr, sizeof(sockaddr)));
First thing to do is to create an ethernet protocol based on the basic_raw_socket class.
You can modify the protocol (htons(ETH_P_ALL)) and the family (PF_PACKET) fields, depending on the traffic you want to send/receive.
/// Create a link-layer protocol associated with a link-layer endpoint
class ll_protocol
{
public:
/// Obtain an identifier for the type of the protocol.
int type() const
{
return SOCK_RAW;
}
/// Obtain an identifier for the protocol.
int protocol() const
{
return protocol_;
}
/// Obtain an identifier for the protocol family.
int family() const
{
return family_;
}
// Construct with a specific family.
explicit ll_protocol(int protocol, int family) :
protocol_(protocol), family_(family)
{
}
explicit ll_protocol() :
protocol_(htons(ETH_P_ALL)), family_(PF_PACKET)
{
}
typedef boost::asio::basic_raw_socket<ll_protocol> socket;
typedef ll_endpoint<ll_protocol> endpoint;
private:
int protocol_;
int family_;
};
To bind the socket to the interface, an endpoint is required. The key point is to create a sockaddr_ll structure, in which the interface to send/receive traffic can be specified.
#include <net/ethernet.h>
#include <sys/socket.h>
#include <linux/if_packet.h>
#include <cstddef>
template <typename Protocol>
class ll_endpoint
{
private:
sockaddr_ll sockaddr;
public:
/// The protocol type associated with the endpoint.
typedef Protocol protocol_type;
typedef boost::asio::detail::socket_addr_type data_type;
/// Constructor
ll_endpoint(const char* ifname)
{
sockaddr.sll_family = PF_PACKET;
sockaddr.sll_protocol = htons(ETH_P_ALL);
sockaddr.sll_ifindex = if_nametoindex(ifname);
sockaddr.sll_hatype = 1;
}
/// Assign from another endpoint.
ll_endpoint& operator=(const ll_endpoint& other)
{
sockaddr = other.sockaddr;
return *this;
}
/// The protocol associated with the endpoint.
protocol_type protocol() const
{
return protocol_type();
}
/// Get the underlying endpoint in the native type.
data_type* data()
{
return &sockaddr;
}
/// Get the underlying endpoint in the native type.
const data_type* data() const
{
return (struct sockaddr*)&sockaddr;
}
/// Get the underlying size of the endpoint in the native type.
std::size_t size() const
{
return sizeof(sockaddr);
}
/// Set the underlying size of the endpoint in the native type.
void resize(std::size_t size)
{
/* nothing we can do here */
}
/// Get the capacity of the endpoint in the native type.
std::size_t capacity() const
{
return sizeof(sockaddr);
}
/// Compare two endpoints for equality.
friend bool operator==(const ll_endpoint<Protocol>& e1,
const ll_endpoint<Protocol>& e2)
{
return e1.sockaddr == e2.sockaddr;
}
/// Compare two endpoints for inequality.
friend bool operator!=(const ll_endpoint<Protocol>& e1,
const ll_endpoint<Protocol>& e2)
{
return !(e1.sockaddr == e2.sockaddr);
}
/// Compare endpoints for ordering.
friend bool operator<(const ll_endpoint<Protocol>& e1,
const ll_endpoint<Protocol>& e2)
{
return e1.sockaddr < e2.sockaddr;
}
/// Compare endpoints for ordering.
friend bool operator>(const ll_endpoint<Protocol>& e1,
const ll_endpoint<Protocol>& e2)
{
return e2.sockaddr < e1.sockaddr;
}
/// Compare endpoints for ordering.
friend bool operator<=(const ll_endpoint<Protocol>& e1,
const ll_endpoint<Protocol>& e2)
{
return !(e2 < e1);
}
/// Compare endpoints for ordering.
friend bool operator>=(const ll_endpoint<Protocol>& e1,
const ll_endpoint<Protocol>& e2)
{
return !(e1 < e2);
}
};
Finally, you can open the socket and connect to the endpoint as follow:
string ifname("eth1");
ll_protocol::socket socket;
socket.open(ll_protocol());
socket.bind(ll_endpoint<ll_protocol>((const char*)ifname.c_str()));
Here is a demo to capture one packet from loopback device with boost asio raw socket:
#include <iostream>
#include <string>
#include <array>
#include <netpacket/packet.h>
#include <net/ethernet.h>
#include <boost/asio.hpp>
#include <boost/format.hpp>
int main()
{
const std::string port = "lo";
std::array<uint8_t, 20> buffer;
uint bytes_received;
sockaddr_ll sockaddr{0};
sockaddr.sll_family = PF_PACKET;
sockaddr.sll_protocol = htons(ETH_P_ALL);
sockaddr.sll_ifindex = if_nametoindex(port.c_str());
sockaddr.sll_hatype = 1;
boost::asio::io_service ios;
boost::asio::generic::raw_protocol::endpoint endpoint(&sockaddr, sizeof(sockaddr), SOCK_RAW);
boost::asio::generic::raw_protocol::socket socket(ios, endpoint);
socket.non_blocking();
bytes_received = socket.receive(boost::asio::buffer(buffer, buffer.size()));
std::cout << "Received " << bytes_received << " bytes" << std::endl;
std::cout << "Source MAC address: ";
std::cout << boost::format("%02X-%02X-%02X-%02X-%02X-%02X") %
buffer[0] % buffer[1] % buffer[2] %
buffer[3] % buffer[4] % buffer[5];
std::cout << std::endl;
std::cout << "Dest MAC address: ";
std::cout << boost::format("%02X-%02X-%02X-%02X-%02X-%02X") %
buffer[6] % buffer[7] % buffer[8] %
buffer[9] % buffer[10] % buffer[11];
std::cout << std::endl;
return 0;
}