Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use a specfic Networkinterface/Ip with Boost Asio sockets?

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).

like image 651
Haatschii Avatar asked Jan 02 '14 19:01

Haatschii


2 Answers

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.)

like image 149
example Avatar answered Nov 16 '22 18:11

example


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.

like image 4
vershov Avatar answered Nov 16 '22 16:11

vershov