Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A semi-concurrent ICMP ping using Boost.Asio on Windows

I have modified the example http://www.boost.org/doc/libs/1_51_0/doc/html/boost_asio/example/icmp/ping.cpp on how to ping a host periodically to ping several hosts concurrently. At first, requests for all hosts are created and send to a socket. Then in the second phase all responses are gathered until timer expires.

The modified example for 3 clients:

// Headers from ping example:
// http://www.boost.org/doc/libs/1_51_0/doc/html/boost_asio/example/icmp/
#include "icmp_header.hpp"
#include "ipv4_header.hpp"

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

using boost::asio::ip::icmp;
using boost::asio::deadline_timer;
using boost::asio::io_service;
using boost::asio::streambuf;
using boost::system::error_code;
using std::cout;
using std::endl;
namespace posix_time = boost::posix_time;

static const std::string BODY = "ping";
static const auto PROCESS = GetCurrentProcessId();

static int gSequence;
static io_service gService;
static icmp::socket gSocket(gService, icmp::v4());
static deadline_timer gTimer(gService);
static streambuf gReply;
static icmp::endpoint gReceiver;

void StartReceive()
{
    gSocket.async_receive_from(gReply.prepare(65536), gReceiver,
        [&](const error_code& error, size_t length)
    {
        gReply.commit(length);

        ipv4_header ipv4Hdr;
        icmp_header icmpHdr;
        std::string body(BODY.size(), 0);

        std::istream is(&gReply);
        is >> ipv4Hdr >> icmpHdr;
        is.read(&body[0], BODY.size());

        auto ip = ipv4Hdr.source_address().to_string();
        auto rc = gReceiver.address().to_string();
        auto id = icmpHdr.identifier();
        auto process = PROCESS;
        auto sn = icmpHdr.sequence_number();
        auto type = icmpHdr.type();

        cout << "  Length              = " << length << endl;
        cout << "  Error               = " << error << endl;
        cout << "  IP checksum         = " << ipv4Hdr.header_checksum() << endl;
        cout << "  IP address          = " << ip << endl;
        cout << "  Receiver address    = " << rc << endl;
        cout << "  ICMP identification = " << id << endl;
        cout << "  ICMP type           = " << (int)type << endl;
        cout << "  Process             = " << process << endl;
        cout << "  Sequence            = " << sn << endl;

        if (is
            && icmpHdr.type() == icmp_header::echo_reply
            && icmpHdr.identifier() == PROCESS
            && icmpHdr.sequence_number() == gSequence
            && body == BODY)
        {
            cout << "    > " << ip << endl;
        }

        cout << endl;

        gReply.consume(length);

        StartReceive();
    });
}

int main()
{
    icmp::resolver resolver(gService);

    icmp_header echoRequest;
    echoRequest.type(icmp_header::echo_request);
    echoRequest.identifier(PROCESS);

    for (gSequence = 0; gSequence < 3; ++gSequence)
    {
        cout << "----------------------------------------------------------" << endl;
        cout << "Iteration = " << gSequence << endl;
        cout << "----------------------------------------------------------" << endl;

        echoRequest.sequence_number(gSequence);
        compute_checksum(echoRequest, BODY.begin(), BODY.end());

        streambuf request;
        std::ostream os(&request);
        os << echoRequest << BODY;

        gService.reset();

        StartReceive();

        std::vector<std::string> pool
        {
            "10.170.110.29",
            "10.170.97.39",
            "10.170.7.52"
        };

        for (const auto & ip : pool)
        {
            icmp::resolver::query query(icmp::v4(), ip, "");
            auto dest = *resolver.resolve(query);

            gSocket.send_to(request.data(), dest);
        }

        gTimer.expires_from_now(posix_time::millisec(2000));
        gTimer.async_wait([&](const error_code& error) { gService.stop(); });

        gService.run();
        gReply.commit(gReply.size());
        gReply.consume(gReply.size());
    }

    return 0;
}

The first iteration (0) works each time as expected although the first packet received has always zero length. However, in all subsequent iterations the responses from one or more clients are not delivered but a response from another client is delivered multiple times instead. Using Wireshark I can see that all hosts in the example very promptly send exactly one response to the request.

This is one of the produced outputs:

----------------------------------------------------------
Iteration = 0
----------------------------------------------------------
  Length              = 0
  Error               = system:10022
  IP checksum         = 0
  IP address          = 0.0.0.0
  Receiver address    = 0.0.0.0
  ICMP identification = 0
  ICMP type           = 0
  Process             = 20464
  Sequence            = 0

  Length              = 32
  Error               = system:0
  IP checksum         = 595
  IP address          = 10.170.97.39
  Receiver address    = 10.170.97.39
  ICMP identification = 20464
  ICMP type           = 0
  Process             = 20464
  Sequence            = 0
    > 10.170.97.39

  Length              = 32
  Error               = system:0
  IP checksum         = 31034
  IP address          = 10.170.110.29
  Receiver address    = 10.170.110.29
  ICMP identification = 20464
  ICMP type           = 0
  Process             = 20464
  Sequence            = 0
    > 10.170.110.29

  Length              = 32
  Error               = system:0
  IP checksum         = 51432
  IP address          = 10.170.7.52
  Receiver address    = 10.170.7.52
  ICMP identification = 20464
  ICMP type           = 0
  Process             = 20464
  Sequence            = 0
    > 10.170.7.52

----------------------------------------------------------
Iteration = 1
----------------------------------------------------------
  Length              = 32
  Error               = system:0
  IP checksum         = 594
  IP address          = 10.170.97.39
  Receiver address    = 10.170.97.39
  ICMP identification = 20464
  ICMP type           = 0
  Process             = 20464
  Sequence            = 1
    > 10.170.97.39

  Length              = 32
  Error               = system:0
  IP checksum         = 51419
  IP address          = 10.170.7.52
  Receiver address    = 10.170.7.52
  ICMP identification = 20464
  ICMP type           = 0
  Process             = 20464
  Sequence            = 1
    > 10.170.7.52

  Length              = 32
  Error               = system:0
  IP checksum         = 51419
  IP address          = 10.170.7.52
  Receiver address    = 10.170.7.52
  ICMP identification = 20464
  ICMP type           = 0
  Process             = 20464
  Sequence            = 1
    > 10.170.7.52    

----------------------------------------------------------
Iteration = 2
----------------------------------------------------------
  Length              = 32
  Error               = system:0
  IP checksum         = 593
  IP address          = 10.170.97.39
  Receiver address    = 10.170.97.39
  ICMP identification = 20464
  ICMP type           = 0
  Process             = 20464
  Sequence            = 2
    > 10.170.97.39

  Length              = 32
  Error               = system:0
  IP checksum         = 51407
  IP address          = 10.170.7.52
  Receiver address    = 10.170.7.52
  ICMP identification = 20464
  ICMP type           = 0
  Process             = 20464
  Sequence            = 2
    > 10.170.7.52

  Length              = 32
  Error               = system:0
  IP checksum         = 51407
  IP address          = 10.170.7.52
  Receiver address    = 10.170.7.52
  ICMP identification = 20464
  ICMP type           = 0
  Process             = 20464
  Sequence            = 2
    > 10.170.7.52

Is this a correct usage and behavior of Boost.Asio ?

Thanks

like image 417
drzn Avatar asked Dec 13 '25 21:12

drzn


1 Answers

Seems fine. It appears to work for me.

Notes:

  • using streambuf seems overly complicated here - I wonder whether the reuse of the streambuf leads to repeated findings of the same contents

  • the stuff can become confused if one of the pool addresses resolves to a local NIC address (because you will receive your own ICMP packets)

  • you never resolve any address beyond the first match, and don't check that resolution worked at all; moreover you resolve each time around (this could be by design, but could also be a mistake. DNS requests also might interfere with your observations (especially if you have a local DNS cache/gateway?).

    Consider using boost::asio::async_resolve and perhaps taking it out of the loop so it doesn't interfere with timings.

Here's a simplified version:

// Headers from ping example:
// http://www.boost.org/doc/libs/1_51_0/doc/html/boost_asio/example/icmp/
#include "icmp_header.hpp"
#include "ipv4_header.hpp"

#include <sys/types.h>
#include <unistd.h>
#include <boost/asio.hpp>
#include <iostream>
#include <sstream>

using boost::asio::ip::icmp;
using boost::asio::deadline_timer;
using boost::asio::io_service;
using boost::asio::streambuf;
using boost::system::error_code;
using std::cout;
using std::endl;
namespace posix_time = boost::posix_time;

static const std::string BODY = "ping";
static const auto PROCESS = getpid();

static int gSequence;
static io_service gService;
static icmp::socket gSocket(gService, icmp::v4());
static char gReply[65536];
static icmp::endpoint gReceiver;

void StartReceive() {
    gSocket.async_receive_from(boost::asio::buffer(gReply), gReceiver, [&](const error_code &error, size_t length) {

        ipv4_header ipv4Hdr;
        icmp_header icmpHdr;
        std::string body(BODY.size(), 0);

        std::istringstream is(std::string(gReply, length));
        is >> ipv4Hdr >> icmpHdr;
        is.read(&body[0], BODY.size());

        auto ip      = ipv4Hdr.source_address().to_string();
        auto rc      = gReceiver.address().to_string();
        auto id      = icmpHdr.identifier();
        auto process = PROCESS;
        auto sn      = icmpHdr.sequence_number();
        auto type    = icmpHdr.type();

        cout << " Length="              << length <<
                " Error="               << error <<
                " IP checksum="         << ipv4Hdr.header_checksum() <<
                " IP address="          << ip <<
                " Receiver address="    << rc <<
                " ICMP identification=" << id <<
                " ICMP type="           << (int)type <<
                " Process="             << process <<
                " Sequence="            << sn << "\n";

        if (is && icmpHdr.type() == icmp_header::echo_reply && icmpHdr.identifier() == PROCESS &&
            icmpHdr.sequence_number() == gSequence && body == BODY) {
            cout << "    > " << ip << endl;
        }

        cout << endl;

        StartReceive();
    });
}

int main() {
    icmp::resolver resolver(gService);

    icmp_header echoRequest;
    echoRequest.type(icmp_header::echo_request);
    echoRequest.identifier(PROCESS);

    for (gSequence = 0; gSequence < 3; ++gSequence) {
        cout << "----------------------------------------------------------" << endl;
        cout << "Iteration=" << gSequence << endl;
        cout << "----------------------------------------------------------" << endl;

        echoRequest.sequence_number(gSequence);
        compute_checksum(echoRequest, BODY.begin(), BODY.end());

        streambuf request;
        std::ostream os(&request);
        os << echoRequest << BODY;

        gService.reset();

        StartReceive();

        for (std::string ip : { "www.msn.com", "www.google.com" }) {
            icmp::resolver::query query(icmp::v4(), ip, "");
            auto dest = *resolver.resolve(query);

            gSocket.send_to(request.data(), dest);
            std::cout << "Sent to " << dest.endpoint() << "\n";
        }

        deadline_timer gTimer(gService);
        gTimer.expires_from_now(posix_time::millisec(2000));
        gTimer.async_wait([&](error_code) { gService.stop(); });

        gService.run();
    }
}

Prints, e.g.

----------------------------------------------------------
Iteration=0
----------------------------------------------------------
Sent to 204.79.197.203:0
Sent to 216.58.212.164:0
 Length=32 Error=system:0 IP checksum=49241 IP address=204.79.197.203 Receiver address=204.79.197.203 ICMP identification=8041 ICMP type=0 Process=8041 Sequence=0
    > 204.79.197.203

 Length=32 Error=system:0 IP checksum=5449 IP address=216.58.212.164 Receiver address=216.58.212.164 ICMP identification=8041 ICMP type=0 Process=8041 Sequence=0
    > 216.58.212.164

----------------------------------------------------------
Iteration=1
----------------------------------------------------------
Sent to 204.79.197.203:0
Sent to 216.58.212.164:0
 Length=32 Error=system:0 IP checksum=49240 IP address=204.79.197.203 Receiver address=204.79.197.203 ICMP identification=8041 ICMP type=0 Process=8041 Sequence=1
    > 204.79.197.203

 Length=32 Error=system:0 IP checksum=5449 IP address=216.58.212.164 Receiver address=216.58.212.164 ICMP identification=8041 ICMP type=0 Process=8041 Sequence=1
    > 216.58.212.164

----------------------------------------------------------
Iteration=2
----------------------------------------------------------
Sent to 204.79.197.203:0
Sent to 216.58.212.164:0
 Length=32 Error=system:0 IP checksum=49239 IP address=204.79.197.203 Receiver address=204.79.197.203 ICMP identification=8041 ICMP type=0 Process=8041 Sequence=2
    > 204.79.197.203

 Length=32 Error=system:0 IP checksum=5449 IP address=216.58.212.164 Receiver address=216.58.212.164 ICMP identification=8041 ICMP type=0 Process=8041 Sequence=2
    > 216.58.212.164
like image 96
sehe Avatar answered Dec 15 '25 13:12

sehe



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!