【问题标题】:Using Boost Asio to accept on IPv6 link scope address使用 Boost Asio 接受 IPv6 链接范围地址
【发布时间】:2012-05-04 09:20:22
【问题描述】:

我有一个使用 Boost ASIO 的 TCP 服务器。我注意到在 Linux 上使用链接范围的 IPv6 地址时,我无法在不引发异常的情况下创建 boost::asio::ip::tcp::acceptor。使用全局 IPv6 地址或 IPv4 地址可以正常工作。

我很确定问题在于未正确设置范围 ID,但我不知道如何解决该问题。

我正在使用 ubuntu 提供的 boost 1.40.0 库在 Ubuntu 11.04 LTS 上进行开发。这是我拥有的服务器代码的一个非常愚蠢的版本,它显示了问题:

#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;
}

在这台机器上,我为 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 

使用 192.168.97.1620.0.0.0127.0.0.1::1 运行程序或 2620:1c:8000:190:20c:29ff:fe10:cf0e 工作正常,但使用 fe80::20c:29ff:fe10:cf0e 无法创建一个接受器抛出“无效参数”异常。

$ ./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!

这让我想起了使用 ping6 并看到相同的“无效参数”错误。解决方法是传递附加到 IPv6 地址的接口。

$ 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

当我尝试从字符串创建 IP 地址时,这似乎不适用于 Boost Asio。

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

我的问题是如何使用 Boost ASIO 监听链接范围的 IPv6 地址? boost::asio::ip::address_v6 类有一个 scope_id() member function,但我不确定从哪里获取无符号长整数的作用域 ID,或者这是否是问题所在。

【问题讨论】:

    标签: c++ boost boost-asio ipv6


    【解决方案1】:

    在命令行中使用ip link ls 或在代码中使用if_nametoindex() 来获取给定设备的接口索引。例如。在我的笔记本电脑上:

    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
    

    在我的例子中,eth0 的接口索引是 2。如果 Boost 只接受数值,则使用它作为范围 ID。

    【讨论】:

      【解决方案2】:

      这可以使用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;
      

      示例会话

      [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]$
      

      【讨论】:

      • 谢谢。这很有效,我喜欢你在 boost::asio 中获得 scope_id 的方式。
      【解决方案3】:

      这是一个使用 spawn 函数的更简单的解决方案。

      #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;
      }
      

      执行结果

      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
      

      【讨论】:

        猜你喜欢
        • 2011-05-10
        • 1970-01-01
        • 2015-09-16
        • 2011-08-18
        • 1970-01-01
        • 2016-04-02
        • 1970-01-01
        • 2010-10-20
        • 1970-01-01
        相关资源
        最近更新 更多