手抜きPINGプログラム

現在,ICMPパケットを扱うソケットクラスを作成しているのですが,そのテストプログラムとしてPINGっぽいプログラムを作成したところ,正常に動作したので取り合えず公開してみます.bkブログ: シンプル=バッドシグナル説と言う記事があったので,シンプルと言わず“手抜き”と明言してみます.動作確認は,cygwin gcc 4.1.2とVisual Studio 2005.

#include <iostream>
#include <string>
#include <cstring>
#include <memory>
#include "clx/icmp.h"
#include "clx/timer.h"
#include "clx/argument.h"
#include "clx/format.h"

int main(int argc, char* argv[]) {
    clx::argument arg(argc, argv);
    if (arg.head().empty()) {
        std::cerr << "usage " << argv[0] << " hostname [-l data_bytes]" << std::endl;
        return -1;
    }
    
    /* bufは送受信兼用バッファ.
     *   -send()メソッド: payload + ICMPヘッダ長
     *   -recv()メソッド: payload + IPヘッダ長 + ICMPヘッダ長
     * 以上を考慮して大きめに領域を確保する.
     */
    clx::icmp::packet_header hdr;
    int payload = 56;
    arg("l,length", payload);
    int packetsize = payload + hdr.icmp_size();
    int buffsize = packetsize + 1024;
    std::auto_ptr<char> buf(new char[buffsize]);
    
    clx::icmp::socket s(arg.head().at(0));
    std::cout << clx::format("ICMP ECHO %s (%s): %d data bytes")
        % arg.head().at(0) % s.to().ipaddr() % payload
    << std::endl;
    
    int seq = 0;
    while (1) {
        std::memset(buf.get(), 'a', packetsize);
        
        // sending ICMP echo request
        hdr.reset();
        hdr.icmp()->type = ICMP_ECHO_REQUEST;
        hdr.icmp()->sequence = seq;
        std::memcpy(buf.get(), (char*)hdr.icmp(), hdr.icmp_size());
        s.send(buf.get(), packetsize);
        clx::timer t;
        
        // receiving ICMP echo packet
        std::memset(buf.get(), 0, buffsize);
        int len = s.recv(buf.get(), buffsize);
        if (len < 0) return -1;
        double recv = t.total_elapsed();
        
        // print information
        hdr = buf.get();
        if (hdr.icmp()->type == ICMP_ECHO_REPLY) {
            std::cout <<
                clx::format("%d bytes from %s: icmp_seq=%d ttl=%d time=%d ms")
                % len % s.from().ipaddr()
                % static_cast<int>(hdr.icmp()->sequence)
                % static_cast<int>(hdr.ip()->ttl)
                % static_cast<int>(recv * 1000)
            << std::endl;
            seq = hdr.icmp()->sequence + 1;
        } else {
            std::cerr << clx::format("received ICMP packet (type: %d) from %s")
                % static_cast<int>(hdr.icmp()->type) % s.from().ipaddr()
            << std::endl;
        }
        
        clx::sleep(1.0);
    }
    
    return 0;
}

指定できるものは,PINGを送るホストとパケットのデータ長のみです.動作は,ICMP echo requestパケットを送って,ICMP echoパケットを受け取って,1秒スリープ,と言う事を永遠に繰り返します.タイムアウト処理などを入れていないので,何らかの問題でパケットが返って来ない場合は永遠にブロックされてしまいます.

以下が実行結果(オリジナルのPINGと比較してみる).

$ ./echotest yahoo.com -l 1000
ICMP ECHO yahoo.com (68.180.206.184): 1000 data bytes
1028 bytes from 68.180.206.184: icmp_seq=0 ttl=46 time=156 ms
1028 bytes from 68.180.206.184: icmp_seq=1 ttl=46 time=159 ms
1028 bytes from 68.180.206.184: icmp_seq=2 ttl=46 time=168 ms
1028 bytes from 68.180.206.184: icmp_seq=3 ttl=46 time=171 ms
1028 bytes from 68.180.206.184: icmp_seq=4 ttl=46 time=162 ms
1028 bytes from 68.180.206.184: icmp_seq=5 ttl=46 time=158 ms

$ ping yahoo.com 1000
PING yahoo.com (68.180.206.184): 1000 data bytes
1008 bytes from 68.180.206.184: icmp_seq=0 ttl=46 time=163 ms
1008 bytes from 68.180.206.184: icmp_seq=1 ttl=46 time=172 ms
1008 bytes from 68.180.206.184: icmp_seq=2 ttl=46 time=171 ms
1008 bytes from 68.180.206.184: icmp_seq=3 ttl=46 time=163 ms
1008 bytes from 68.180.206.184: icmp_seq=4 ttl=46 time=178 ms
1008 bytes from 68.180.206.184: icmp_seq=5 ttl=46 time=162 ms

受信バイト数が異なっていますね.ICMPソケットで通信する場合,recv()メソッドで受け取るデータにはIPヘッダが付与されているのですが,オリジナルのPINGプログラムはそのサイズ分は省いて表示しているようです.それと,(オリジナルのPINGがどうやっているのかは追っていないので知りませんが)テストプログラムは,RTTを計測する際にタイムスタンプは使わず,send()が成功した直後からrecv()が成功した直後までの時間を表示しているのですが,これはそこそこいい値で測れているようです.

ICMPソケットクラスを作ったのは,ICMP ECHOを利用して(ちょっと賢い)帯域計測プログラムを作るためで,そちらも完成したら公開できればと思います.時刻取得関数にかなり厳しい精度を要求されるので(100μ秒程度),きちんと使えるかどうかは怖いところですが・・・