5

I have a Debian/linux server which has several Ip adresses, all assigned to the same physical network card. The /etc/network/interfaces config file looks like this (the xx represent numbers)

auto lo
iface lo inet loopback

auto eth0
iface eth0 inet static
    address 176.xx.xx.144
    netmask 255.255.255.0
    network 176.xx.xx.0
    broadcast 176.xx.xx.255
    gateway 176.xx.xx.254

auto eth0:0
allow-hotplug eth0:0
iface eth0:0 inet static
    address 46.xx.xx.57
    netmask 255.255.255.255
    broadcast 46.xx.xx.57

auto eth0:1
allow-hotplug eth0:1
iface eth0:1 inet static
    address 94.xx.xx.166
    netmask 255.255.255.255
    broadcast 94.xx.xx.166

//IPv6 Stuff...

I am working on a client application that uses Boost Asio to handle all network connections. In this Application I want to be able to connect to an external server using a specific networkinterface/Ip address. I found this similar question, however simply binding a boost::asio::ip::tcp::socket to a specfic endpoint and then connect to an external Server doesn't work. Here is a minimal working example of what I tried:

#include <iostream>
#include <boost/asio.hpp>

int main( int argC, char *argV[] ) {
    boost::asio::io_service ioService;
    boost::asio::ip::tcp::socket socket(ioService);

    boost::asio::ip::tcp::endpoint localEndpoint(
        boost::asio::ip::address::from_string("94.xx.xx.166"), 0);

    boost::asio::ip::tcp::resolver resolver(ioService);
    boost::asio::ip::tcp::resolver::iterator remoteEndpoint = 
        resolver.resolve(boost::asio::ip::tcp::resolver::query("haatschii.de", "80"));

    socket.open(boost::asio::ip::tcp::v4());

    std::cout   << "Before binding socket has local endpoint: " 
                << socket.local_endpoint().address().to_string() 
                << ":" << socket.local_endpoint().port() << std::endl;

    socket.bind(localEndpoint);

    std::cout   << "Before connecting socket has local endpoint: " 
                << socket.local_endpoint().address().to_string() 
                << ":" << socket.local_endpoint().port() << std::endl;

    boost::asio::connect(socket, remoteEndpoint);

    std::cout   << "After connecting socket has local endpoint: " 
                << socket.local_endpoint().address().to_string() 
                << ":" << socket.local_endpoint().port() << std::endl;

    //Test request to a page that echos our IP address.
    boost::asio::write(socket, 
        boost::asio::buffer("GET /ip.php HTTP/1.1\r\nHost: haatschii.de\r\nAccept: */*\r\n\r\n", 57));

    //Parse server response (not important for this code example)
    return 0;
}

When I run this on my server I get:

Before binding socket has local endpoint: 0.0.0.0:0
Before connecting socket has local endpoint: 94.xx.xx.166:38399
After connecting socket has local endpoint: 176.xx.xx.144:45959
External server says we are using IP: 176.xx.xx.144

Right now I am a bit lost, because I don't know what else to try. I don't necessarily need a portable solution for this, anything that works with this Debian setup will do.

Update

I'll offer the bounty for a solution that works for my setup. If necessary I can change the /etc/network/interfaces config file. However in order to reuse my code, any solution has to work with Boost Asio sockets (at least as a wrapper).

Community
  • 1
  • 1
Haatschii
  • 9,021
  • 10
  • 58
  • 95
  • The basic approach in the linked question appears correct, though (and I asked there) I've never used the `lowest_layer()` function, and usually just `open()` and `bind()` directly using the member functions of the `socket` object. – Chad Jan 02 '14 at 19:56
  • @Chad So the Approach `open()->bind()->connect()` worked for you? I'll test it without the `lowest_layer()`, however I can hardly imagine that this is the problem (see other question). Or do you by chance have a code example which works? – Haatschii Jan 02 '14 at 20:14
  • Why did you make a duplicate of your [own question](http://stackoverflow.com/questions/20686226/local-endpoint-of-boostasioiptcpsocket-is-changed-during-connect)? Anyways look at the [Boost Asio examples](http://www.boost.org/doc/libs/1_55_0/doc/html/boost_asio/examples/cpp11_examples.html). – Steve Van Opstal Jan 02 '14 at 20:29
  • `open()` + `bind()` seems to work for us. I mostly deal with the receiving end, and generally with `UDP multicast`. For `TCP`, we ensure that the static routes are setup on the box properly, but I do know that if we `bind()` to the wrong interface then our `TCP` connections will fail. – Chad Jan 02 '14 at 20:36
  • @SteveVanOpstal, the other question was about, why some specific approach didn't work, not about the canonical way to achieve this as this question is. However I decided to merge them into this one, since you are somehow right that the other one makes no sense anymore. I know the Boost Asio examples, but I don't see how they answer my question... – Haatschii Jan 06 '14 at 21:12
  • I think this might be a routing issue. Your OS simply picked another network for this connection. – Fozi Jan 06 '14 at 21:17
  • @Fozi: Yes somehow the OS seems to override my socket settings. But it does work with curl (Added Update to the question), so there must be a way to prevent the OS from doing so... – Haatschii Jan 06 '14 at 23:33
  • I had the same thing except with udp instead of tcp. Then again i did not need it to be changeable so i could just change the standard route to the subnet of the desired host. Out of curiosity, what does `sudo route -n` give you? – DeVadder Jan 07 '14 at 15:50

2 Answers2

8

To bind to a specific interface you have to open the connection first. You do that - so far so good. But after that you call boost::asio::connect(socket, remoteEndpoint); which will close the connection for you (as a service so to say).

Boost tells you that it does so - but you have to look closely. In the reference under parameters for the overloaded version of connect you are using it will say

Parameters

s

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

or in its implementation in boost/asio/impl/connect.hpp:

// Copyright (c) 2003-2011 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

[...]

template <typename Protocol, typename SocketService,
    typename Iterator, typename ConnectCondition>
Iterator connect(basic_socket<Protocol, SocketService>& 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 = connect_condition(ec, iter);
    if (iter != end)
    {
      s.close(ec);
      s.connect(*iter, ec);
      if (!ec)
        return iter;
    }
  }

  if (!ec)
    ec = boost::asio::error::not_found;

  return end;
}

(note the s.close(ec);)


The solution

should be simple. Replace boost::asio::connect... by

socket.connect(*remoteEndpoint);

(or a loop over the respective remote endpoints, similar to the boost sourcecode, if necessary.)

example
  • 3,349
  • 1
  • 20
  • 29
  • It's as simple as that! Thanks – Haatschii Jan 07 '14 at 23:11
  • I've exactly the same problem as the writer of this question, but with a `boost::asio::ip::udp::socket`. If i bind and connect the socket, I am unable to receive data via the bound and connected socket. I am not retrieving an exception, though I wonder what I am doing wrong. I know that UDP is in connectionless, but why does the Boost.Asio API offer both methods then? – Florian Wolters Aug 28 '14 at 14:34
  • If I read the answer provided [here](http://stackoverflow.com/questions/10644115/connect-on-connection-less-boostasioipudpsocket), I would expect that both using bind and connect should work in a way that: 1. The bound socket address is used for all calls to `[async]_receive`. 2. The connected socket address is used for all calls to `[async]_send`. Therefore, if using `connect` there is no need to use the `receive_from` and `send_to` methods. Am I missing something? I am using Boost 1.49.0 by the way. – Florian Wolters Aug 28 '14 at 14:51
5

Generally, you could use the following workflow:

void connect_handler(const boost::system::error_code& error)
{
  if (!error) { // Connect succeeded.
  }
}
...
boost::asio::ip::tcp::socket socket(io_service);
boost::asio::ip::tcp::endpoint remote_endpoint(
    boost::asio::ip::address::from_string("1.2.3.4"), 12345); // server address
socket.open(boost::asio::ip::tcp::v4());
socket.bind(boost::asio::ip::tcp::endpoint(
    boost::asio::ip::address::from_string("1.2.3.55"), // your local address
    7777)
);
socket.async_connect(remote_endpoint, connect_handler);

More info could be found here.

vershov
  • 928
  • 4
  • 6