I am making a client socket.
To make things easier for my testers, I'd like to specify the network card and port that the socket will use.
Yesterday, in my Google search, I found: Binding boost asio to local tcp endpoint
By performing the open, bind, and async_connect, I was able to bind to a specific network card and I started seeing traffic in Wireshark.
However, Wireshark reports that the socket has been given a random port rather than the one I specified. I would think if the port was in use it would have filled out the error_code passed to the bind method.
What am I doing wrong?
Here is my minimal example, extracted and edited from my real solution.
// Boost Includes
#include <boost/asio.hpp>
#include <boost/atomic.hpp>
#include <boost/bind.hpp>
#include <boost/thread.hpp>
#include <boost/thread/condition_variable.hpp>
// Standard Includes
#include <exception>
#include <memory>
#include <string>
#include <sstream>
boost::asio::io_service g_ioService; /** ASIO sockets require an io_service to run on*/
boost::thread g_thread; /** thread that will run the io_service and hence where callbacks are called*/
boost::asio::ip::tcp::socket g_socket(g_ioService); /** Aync socket*/
boost::asio::ip::tcp::resolver g_resolver(g_ioService); /** Resolves IP Addresses*/
//--------------------------------------------------------------------------------------------------
void OnConnect(const boost::system::error_code & errorCode, boost::asio::ip::tcp::resolver::iterator endpoint)
{
if (errorCode || endpoint == boost::asio::ip::tcp::resolver::iterator())
{
// Error - An error occured while attempting to connect
throw std::runtime_error("An error occured while attempting to connect");
}
// We connected to an endpoint
/*
// Start reading from the socket
auto callback = boost::bind(OnReceive, boost::asio::placeholders::error);
boost::asio::async_read_until(g_socket, m_receiveBuffer, '\n', callback);
*/
}
//--------------------------------------------------------------------------------------------------
void Connect()
{
const std::string hostName = "10.84.0.36";
const unsigned int port = 1007;
// Resolve to translate the server machine name into a list of endpoints
std::ostringstream converter;
converter << port;
const std::string portAsString = converter.str();
boost::asio::ip::tcp::resolver::query query(hostName, portAsString);
boost::system::error_code errorCode;
boost::asio::ip::tcp::resolver::iterator itEnd;
boost::asio::ip::tcp::resolver::iterator itEndpoint = g_resolver.resolve(query, errorCode);
if (errorCode || itEndpoint == itEnd)
{
// Error - Could not resolve either machine
throw std::runtime_error("Could not resolve either machine");
}
g_socket.open(boost::asio::ip::tcp::v4(), errorCode);
if (errorCode)
{
// Could open the g_socket
throw std::runtime_error("Could open the g_socket");
}
boost::asio::ip::tcp::endpoint localEndpoint(boost::asio::ip::address::from_string("10.86.0.18"), 6000);
g_socket.bind(localEndpoint, errorCode);
if (errorCode)
{
// Could bind the g_socket to local endpoint
throw std::runtime_error("Could bind the socket to local endpoint");
}
// Attempt to asynchronously connect using each possible end point until we find one that works
boost::asio::async_connect(g_socket, itEndpoint, boost::bind(OnConnect, boost::asio::placeholders::error, boost::asio::placeholders::iterator));
}
//--------------------------------------------------------------------------------------------------
void g_ioServiceg_threadProc()
{
try
{
// Connect to the server
Connect();
// Run the asynchronous callbacks from the g_socket on this thread
// Until the io_service is stopped from another thread
g_ioService.run();
}
catch (...)
{
throw std::runtime_error("unhandled exception caught from io_service g_thread");
}
}
//--------------------------------------------------------------------------------------------------
int main()
{
// Start up the IO service thread
g_thread.swap(boost::thread(g_ioServiceg_threadProc));
// Hang out awhile
boost::this_thread::sleep_for(boost::chrono::seconds(60));
// Stop the io service and allow the g_thread to exit
// This will cancel any outstanding work on the io_service
g_ioService.stop();
// Join our g_thread
if (g_thread.joinable())
{
g_thread.join();
}
return true;
}
As you can see in the following screenshot, a random port 32781 was selected rather than my requested port 6000.
I doubt topic starter is still interested in this question, but for all of future seekers like myself, here is the solution.
The issue here is that boost::asio::connect
closes the socket before calling connect
for every endpoint in the provided range:
From boost/asio/impl/connect.hpp:
template <typename Protocol BOOST_ASIO_SVC_TPARAM,
typename Iterator, typename ConnectCondition>
Iterator connect(basic_socket<Protocol BOOST_ASIO_SVC_TARG>& s,
Iterator begin, Iterator end, ConnectCondition connect_condition,
boost::system::error_code& ec)
{
ec = boost::system::error_code();
for (Iterator iter = begin; iter != end; ++iter)
{
iter = (detail::call_connect_condition(connect_condition, ec, iter, end));
if (iter != end)
{
s.close(ec); // <------
s.connect(*iter, ec);
if (!ec)
return iter;
}
...
}
That is why bound address is reset. To keep it bound one can use socket.connect/async_connect(...)
directly
6000 is the remote endpoint port, and it is correctly used (otherwise, you wouldn't be connecting to the server side).
From: https://idea.popcount.org/2014-04-03-bind-before-connect/
A TCP/IP connection is identified by a four element tuple: {source IP, source port, destination IP, destination port}. To establish a TCP/IP connection only a destination IP and port number are needed, the operating system automatically selects source IP and port.
Since you do not bind to a local port, one is selected randomly from the "ephemeral port range". This is, by far, the usual way to connect.
Fear not:
It is possible to ask the kernel to select a specific source IP and port by calling
bind()
before callingconnect()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # Let the source address be 192.168.1.21:1234 s.bind(("192.168.1.21", 1234)) s.connect(("www.google.com", 80))
The sample is python.
You do that, but still get another port. It's likely that the hint port is not available.
Check the information on SO_REUSEADDR
and SO_REUSEPORT
in the linked article
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With