Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Boost Asio to accept on IPv6 link scope address

I have a TCP server that uses Boost ASIO. I've noticed that when using link scoped IPv6 addresses on Linux I can't create a boost::asio::ip::tcp::acceptor without throwing an exception. Using a Global IPv6 address or an IPv4 address will work fine.

I'm pretty sure the problem is with the scope id not being set correctly but I can't figure out how to fix the problem.

I'm developing on Ubuntu 11.04 LTS using the ubuntu provided boost 1.40.0 library. Here's a very dumbed down version of the server code I have that shows the problem:

#include <boost/asio.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/bind.hpp>
#include <iostream>
#include <string>

/* To Compile:
g++ -Wall -o ./asio-ipv6 ./asio-ipv6.cpp -lboost_system 
*/

typedef boost::shared_ptr<boost::asio::ip::tcp::socket>   TcpSocketPtr;
typedef boost::shared_ptr<boost::asio::ip::tcp::acceptor> TcpAcceptorPtr;
typedef boost::shared_ptr<boost::asio::ip::tcp::endpoint> TcpEndpointPtr;

class AsioServer{
public:
  AsioServer(boost::asio::io_service& io): io_(io){};

  //throws
  void accept(const std::string& ipString,unsigned short port){
    boost::asio::ip::address addr = boost::asio::ip::address::from_string(ipString);
    std::cout << "Valid IP address " << ipString << std::endl;

    this->endpoint_.reset(new boost::asio::ip::tcp::endpoint(addr,port));
    std::cout << "Created endpoint" << std::endl;

    //Will throw if a link local IPv6 address is used
    acceptor_.reset(new boost::asio::ip::tcp::acceptor(this->io_,*(this->endpoint_)));

    std::cout << "About to accept on " << *(this->endpoint_) << std::endl;
    this->socket_.reset(new boost::asio::ip::tcp::socket(this->io_));
    this->acceptor_->async_accept(*socket_ ,boost::bind(&AsioServer::handle_accept,this,boost::asio::placeholders::error)); 
  }

private:
  boost::asio::io_service& io_;
  TcpSocketPtr socket_;
  TcpAcceptorPtr acceptor_;
  TcpEndpointPtr endpoint_;

  void handle_accept(const boost::system::error_code &ec){
    if(!ec){
      std::cout << "Accepted connection!" << std::endl;
    }
    else{
      std::cout << "Error accepting connection" << std::endl;
    }
  };

};

int main(int argc, char* argv[]){

  boost::asio::io_service io;

  std::string ipString("0.0.0.0");
  if(argc > 1){
    ipString.assign(argv[1]);   
  }
  std::cout << "IP Set to " << ipString << std::endl;

  AsioServer server(io);

  try{
    server.accept(ipString,4444);
  }
  catch(const std::exception& e){
    std::cout << "Error caught:  " << e.what() << std::endl;
  }

  io.run();

  std::cout << "Done!" << std::endl;

  return 0;
}

On this machine I have the following configured for eth0:

$ ifconfig eth0
eth0      Link encap:Ethernet  HWaddr 00:0c:29:10:cf:0e  
          inet addr:192.168.97.162  Bcast:192.168.97.255  Mask:255.255.255.0
          inet6 addr: 2620:1c:8000:190:20c:29ff:fe10:cf0e/64 Scope:Global
          inet6 addr: fe80::20c:29ff:fe10:cf0e/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:112470185 errors:2 dropped:1 overruns:0 frame:0
          TX packets:5900249 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:2187237130 (2.1 GB)  TX bytes:1640094885 (1.6 GB)
          Interrupt:19 Base address:0x2000 

Running the program using 192.168.97.162, 0.0.0.0, 127.0.0.1, ::1 or 2620:1c:8000:190:20c:29ff:fe10:cf0e works fine but using fe80::20c:29ff:fe10:cf0e fails to create an acceptor with an "Invalid Argument" exception being thrown.

$ ./asio-ipv6 fe80::20c:29ff:fe10:cf0e
IP Set to fe80::20c:29ff:fe10:cf0e
Valid IP address fe80::20c:29ff:fe10:cf0e
Created endpoint
Error caught:  Invalid argument
Done!

This reminds me of using ping6 and seeing the same "Invalid Argument" error. The fix is to pass the the interface appended to the IPv6 address.

$ ping6 fe80::219:b9ff:fe2b:3a53  #Won't work
connect: Invalid argument
$ ping6 fe80::219:b9ff:fe2b:3a53%eth0 #Pass the interface to use to ping6 
PING fe80::219:b9ff:fe2b:3a53%eth0(fe80::219:b9ff:fe2b:3a53) 56 data bytes

This doesn't seem to work for Boost Asio when I try to create the IP address from a string.

$ ./asio-ipv6 fe80::20c:29ff:fe10:cf0e%eth0
IP Set to fe80::20c:29ff:fe10:cf0e%eth0
Error caught:  Invalid argument
Done!

My question is how do you go about listening on a link scoped IPv6 address using Boost ASIO? The boost::asio::ip::address_v6 class has a scope_id() member function but I'm not sure where to get the scope id as an unsigned long or if this is even the problem.

like image 238
Joel Avatar asked Apr 23 '12 18:04

Joel


3 Answers

This can be accomplished using a boost::asio::ip::tcp::resolver

--- ipv6.cc.orig        2012-04-24 12:03:02.349911481 -0500
+++ ipv6.cc     2012-04-24 12:02:07.053037095 -0500
@@ -10,42 +10,67 @@

 typedef boost::shared_ptr<boost::asio::ip::tcp::socket>   TcpSocketPtr;
 typedef boost::shared_ptr<boost::asio::ip::tcp::acceptor> TcpAcceptorPtr;
+typedef boost::shared_ptr<boost::asio::ip::tcp::resolver> TcpResolverPtr;
 typedef boost::shared_ptr<boost::asio::ip::tcp::endpoint> TcpEndpointPtr;

 class AsioServer{
 public:
   AsioServer(boost::asio::io_service& io): io_(io){};

-  //throws
-  void accept(const std::string& ipString,unsigned short port){
-    boost::asio::ip::address addr = boost::asio::ip::address::from_string(ipString);
-    std::cout << "Valid IP address " << ipString << std::endl;
-
-    this->endpoint_.reset(new boost::asio::ip::tcp::endpoint(addr,port));
-    std::cout << "Created endpoint" << std::endl;
-
-    //Will throw if a link local IPv6 address is used
-    acceptor_.reset(new boost::asio::ip::tcp::acceptor(this->io_,*(this->endpoint_)));
-
-    std::cout << "About to accept on " << *(this->endpoint_) << std::endl;
-    this->socket_.reset(new boost::asio::ip::tcp::socket(this->io_));
-    this->acceptor_->async_accept(*socket_ ,boost::bind(&AsioServer::handle_accept,this,boost::asio::placeholders::error)); 
+  void resolve(const std::string& ipString, const std::string& service){
+    this->resolver_.reset(new boost::asio::ip::tcp::resolver(io_));
+    boost::asio::ip::tcp::resolver::query query( ipString, service );
+    this->resolver_->async_resolve( query, boost::bind( &AsioServer::handle_resolve, this, boost::asio::placeholders::error, boost::asio::placeholders::iterator ) );
   }
-
 private:
   boost::asio::io_service& io_;
   TcpSocketPtr socket_;
   TcpAcceptorPtr acceptor_;
   TcpEndpointPtr endpoint_;
+  TcpResolverPtr resolver_;
+
+  void accept(const boost::asio::ip::tcp::resolver::iterator iterator) {
+    endpoint_.reset(new boost::asio::ip::tcp::endpoint(iterator->endpoint()));
+    //Will throw if a link local IPv6 address is used
+    acceptor_.reset(new boost::asio::ip::tcp::acceptor(this->io_));
+    acceptor_->open(this->endpoint_->protocol());
+    acceptor_->bind(*(this->endpoint_));
+    acceptor_->listen();
+
+
+    std::cout << "About to accept on " << *(this->endpoint_) << std::endl;
+    socket_.reset(new boost::asio::ip::tcp::socket(this->io_));
+    acceptor_->async_accept(*socket_ ,boost::bind(&AsioServer::handle_accept,this,boost::asio::placeholders::error)); 
+  }
+

   void handle_accept(const boost::system::error_code &ec){
     if(!ec){
       std::cout << "Accepted connection!" << std::endl;
     }
     else{
-      std::cout << "Error accepting connection" << std::endl;
+      std::cout << "Error accepting connection: " << boost::system::system_error(ec).what() << std::endl;
+    }
+  }
+  
+  void handle_resolve(
+        const boost::system::error_code& ec,
+        boost::asio::ip::tcp::resolver::iterator iterator
+       )
+  {
+    if(ec) {
+      std::cerr << "could not resolve: " << boost::system::system_error(ec).what() << std::endl;
+      return;
     }
-  };
+
+    if ( iterator == boost::asio::ip::tcp::resolver::iterator() ) {
+      std::cerr << "no endpoints resolved" << std::endl;
+      return;
+    }
+
+    this->accept(iterator);
+
+  }

 };

@@ -62,7 +87,7 @@
   AsioServer server(io);

   try{
-    server.accept(ipString,4444);
+    server.resolve(ipString,"4444");
   }
   catch(const std::exception& e){
     std::cout << "Error caught:  " << e.what() << std::endl;

sample session

[samm@t410 Desktop]$ ifconfig | grep fe80
          inet6 addr: fe80::5a94:6bff:fe7c:e760/64 Scope:Link
[samm@t410 Desktop]$ g++ -g ipv6.cc -lboost_system -lboost_thread-mt
[samm@t410 Desktop]$ ./a.out fe80::5a94:6bff:fe7c:e760%wlan0
IP Set to fe80::5a94:6bff:fe7c:e760%wlan0
About to accept on [fe80::5a94:6bff:fe7c:e760%wlan0]:4444
^C
[samm@t410 Desktop]$
like image 113
Sam Miller Avatar answered Nov 20 '22 18:11

Sam Miller


Use ip link ls from the command line or if_nametoindex() in your code to get the interface index for a given device. E.g. on my laptop:

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 16436 qdisc noqueue state UNKNOWN 
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN qlen 1000
    link/ether f0:de:f1:5c:39:37 brd ff:ff:ff:ff:ff:ff

In my case the interface index for eth0 is 2. Use this as the scope id if Boost only accepts a numerical value.

like image 31
ldx Avatar answered Nov 20 '22 19:11

ldx


This is a simpler solution using the spawn function.

#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/bind.hpp>
#include <iostream>
#include <string>

class AsioServer{
public:
    AsioServer(boost::asio::io_service& io): m_io(io) {}

    void accept(const std::string& ip_address, const std::string& service) {
        boost::asio::spawn(m_io, [this, ip_address, service](boost::asio::yield_context yield) {
            try {
                using tcp = boost::asio::ip::tcp;
                boost::system::error_code ec;
                tcp::resolver resolver(m_io);
                tcp::resolver::iterator iterator =
                        resolver.async_resolve(tcp::resolver::query(ip_address, service), yield);
                if (iterator == tcp::resolver::iterator()) {
                    std::cerr << "No endpoints resolved for "<< ip_address  << std::endl;
                    return;
                }
                tcp::endpoint endpoint(iterator->endpoint());
                std::cout << "Endpoint: " << endpoint << std::endl;
                tcp::acceptor acceptor(m_io);
                acceptor.open(endpoint.protocol());
                acceptor.bind(endpoint);
                acceptor.listen();
                std::cout << "About to accept on " << endpoint << std::endl;
                for (;;)
                {
                  tcp::socket socket(m_io);
                  acceptor.async_accept(socket, yield[ec]);
                  if (ec) {
                      std::cerr << "Error accepting connection: " << boost::system::system_error(ec).what() << std::endl;
                  } else {
                      std::cout << "Accepted connection from " << socket.remote_endpoint() << std::endl;
                      //std::make_shared<session>(std::move(socket))->go();
                  }
                }
            }
            catch( std::exception &e) {
                std::cerr << "Error setting up accepting port: " << e.what() << std::endl;
            }
          });
    }

private:
    boost::asio::io_service& m_io;
};

int main(int argc, char* argv[])
{
    boost::asio::io_service io;
    std::string ipString("0.0.0.0");

    if(argc > 1){
      ipString.assign(argv[1]);
    }
    std::cout << "IP Set to " << ipString << std::endl;

    AsioServer server(io);

    try{
      server.accept(ipString,"4444");
    }
    catch(const std::exception& e){
      std::cout << "Error caught:  " << e.what() << std::endl;
    }

    io.run();
    std::cout << "Done!" << std::endl;
    return 0;
}

Execution result

AsioServer$ ifconfig | grep fe80
          adr inet6: fe80::be5f:f4ff:fef7:8a46/64 Scope:Lien
AsioServer$ ./a_out fe80::be5f:f4ff:fef7:8a46%eth0
IP Set to fe80::be5f:f4ff:fef7:8a46%eth0
Endpoint: [fe80::be5f:f4ff:fef7:8a46%eth0]:4444
About to accept on [fe80::be5f:f4ff:fef7:8a46%eth0]:4444
like image 1
chmike Avatar answered Nov 20 '22 19:11

chmike