socketsboosttcpboost-asionat-traversal

Can't set TCP source Port with boost asio


Usually it does not matter which source port you send data from as a client, but I still want to do it for some testing. So I tried to bind my client's socket to a specific port but even when I'm running client and server on my local machine (with localhost as dest address) the server tells me that my source port is something like 59000. I initialize my socket like this:

tcp::socket socket(io_service,tcp::endpoint(tcp::v4(),2000));

Is it possible to do what I intend? I'm trying to find out if my router changes ports when the message goes through it. Thats for NAT traversal stuff I'm playing around with at the moment.


Solution

  • Binding a socket to a specific port can be done exactly as posted in the question:

    using boost::asio::ip::tcp;
    boost::asio::io_service io_service;
    tcp::socket socket(io_service, tcp::endpoint(tcp::v4(), 2000));
    assert(socket.local_endpoint().port() == 2000); // true
    

    In this case, the socket object will be constructed, opening and binding to the local endpoint with an address of INADDR_ANY and port 2000.

    The local endpoint is likely changing as a result of how the connection is being established. When the connect operation is initiated from either the socket.connect() or socket.async_connect() member functions, the socket will attempt to connect to the remote endpoint, opening the socket if necessary. Therefore, when invoked on a socket that is already open, the socket's local endpoint will not change.

    On the other hand, when the connect operation is initiated from either connect() or async_connect() free functions, the socket is closed before attempting to connect to any endpoint. Thus, the socket will bind to an unspecified port. The parameter section on the free functions documentation defines this behavior:

    The socket to be connected. If the socket is already open, it will be closed.

    Furthermore, there is no clean way to control this behavior, as the socket.close() and socket.connect() member functions are invoked one after another within the implementation.


    Here is a complete example demonstrating the behaviors described above:

    #include <iostream>
    #include <boost/asio.hpp>
    #include <boost/bind.hpp>
    #include <boost/lexical_cast.hpp>
    
    // This example is not interested in the handlers, so provide a noop function
    // that will be passed to bind to meet the handler concept requirements.
    void noop() {}
    
    // Helper function used to initialize the client socket.
    void force_endpoint(boost::asio::ip::tcp::socket& client_socket)
    {
      using boost::asio::ip::tcp;
      client_socket.close();
      client_socket.open(tcp::v4());
      client_socket.bind(tcp::endpoint(tcp::v4(), 2000));
      std::cout << "client socket: " << client_socket.local_endpoint()
                << std::endl;
    }
    
    int main()
    {
      using boost::asio::ip::tcp;
      boost::asio::io_service io_service;
    
      // Create all I/O objects.
      tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), 0));
      tcp::socket server_socket(io_service);
      tcp::socket client_socket(io_service);
    
      // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      // Initiate the connect operation directly on the socket.
      server_socket.close();
      force_endpoint(client_socket);
      acceptor.async_accept(server_socket, boost::bind(&noop));
      client_socket.async_connect(acceptor.local_endpoint(), boost::bind(&noop));
    
      // Print endpoints before and after running the operations.
      io_service.run();
      std::cout << "After socket.async_connect(): "
                << client_socket.local_endpoint() << std::endl;
    
      // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      // Initiate the connection operation with the free async_connect function.
      server_socket.close();
      force_endpoint(client_socket);
      acceptor.async_accept(server_socket, boost::bind(&noop));
      boost::asio::async_connect(
          client_socket,
          tcp::resolver(io_service).resolve(
            tcp::resolver::query("127.0.0.1", 
              boost::lexical_cast<std::string>(acceptor.local_endpoint().port()))),
          boost::bind(&noop));
    
      // Run the service, causing the client to connect to the acceptor.
      io_service.reset();
      io_service.run();
      std::cout << "After async_connect(): " 
                << client_socket.local_endpoint() << std::endl;
    }
    

    Which produced the following output:

    client socket: 0.0.0.0:2000
    After socket.async_connect(): 127.0.0.1:2000
    client socket: 0.0.0.0:2000
    After async_connect(): 127.0.0.1:53115