CLX C++ Libraries - icmp::socket
Author: clown

CLX C++ Libraries
Home >> icmp::socket

# Declaration

    #define ICMP_ECHO_REQUEST 8
    #define ICMP_ECHO_REPLY 0

    namespace icmp {
        template <int Family>
        class basic_socket : public basic_rawsocket<SOCK_RAW, Family, IPPROTO_ICMP>

        typedef basic_rawsocket<SOCK_RAW, AF_INET, IPPROTO_ICMP> rawsocket;
        typedef basic_socket<AF_INET> socket;
        typedef basic_sockaddress<AF_INET, IPPROTO_ICMP> sockaddress;
        typedef basic_sockmanager<SOCK_RAW, AF_INET, IPPROTO_ICMP> sockmanager;
    };

# Overview

icmp::socket は，ICMP 用ソケットのラッパクラスです．現在は，ICMP ECHO REQUEST の送信，および ICMP ECHO REPLY の受信しか想定していません（値を define していない）． IPヘッダおよびICMPヘッダは，それぞれ ip.h，icmp.h のグローバル名前空間において， 独自に定義しています．

    struct iphdr {
        u_int8_t  ihl:4;
        u_int8_t  version:4;
        u_int8_t  tos;

        u_int16_t tot_len;
        u_int16_t id;
        u_int16_t frag_off;

        u_int8_t  ttl;
        u_int8_t  protocol;
        u_int16_t check;

        unsigned long saddr;
        unsigned long daddr;
    };

    struct icmphdr {
        u_int8_t  type;
        u_int8_t  code;
        u_int16_t checksum;
        u_int16_t id;
        u_int16_t sequence;
    };

また，データに付与されているIPヘッダ，ICMPヘッダを扱うための packet_header も定義しています．packet_header は，（IP ヘッダ + ICMP ヘッダが付与されている） 受信データを引数として指定されると，受信データからヘッダ情報をコピーします．

    namespace icmp {
        class packet_header {
        public:
            typedef struct iphdr ip_type;
            typedef struct icmphdr icmp_type;
            typedef size_t size_type;

            packet_header();
            explicit packet_header(const char* packet);
            virtual ~packet_header();

            packet_header& operator=(const char* packet);
            packet_header& assign(const char* packet);
            void reset();

            size_type size() const;
            size_type ip_size() const;
            size_type icmp_size() const;

            ip_type* ip();
            icmp_type* icmp();

            const ip_type* ip() const;
            const icmp_type* icmp() const;
        };
    };

ICMP ソケットを用いて通信を行う場合，送信時にはデータの先頭に ICMP ヘッダを付与して送信する必要があります．また，受信されるデータには， IP ヘッダ，および ICMP ヘッダが付与されています．

# Example

example_icmp.cpp

    #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]);

        try {
            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);
            }
        }
        catch (clx::socket_error& e) {
            std::cerr << e.what() << std::endl;
            return -1;
        }
        catch (clx::sockaddress_error& e) {
            std::cerr << e.what() << std::endl;
            return -1;
        }

        return 0;
    }

    Result
    $ ./test 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 のような動きをします．指定されたホスト名に対して，

1. ICMP ECHO REQUEST を送信する．
2. ICMP ECHO REPLY を受信する．
3. 1 秒スリープする．

と言う動きを繰り返します．RTT の計測にはタイムスタンプは使わずに，send() メソッドが成功した直後から recv() メソッドが成功した直後までの時間を表示しています．

# Template Parameters

Family
  プロトコルファミリーを指定します．

# Related Types

    typedef basic_rawsocket<SOCK_RAW, Family, IPPROTO_ICMP> rawsocket;
    typedef basic_sockaddress<Family, IPPROTO_ICMP> sockaddress;
    typedef char char_type;
    typedef std::basic_string<char_type> string_type;

# Construction and Member Functions

    basic_socket();
    basic_socket(const basic_socket& cp);
    basic_soket& operator=(const basic_socket& cp);

    explicit basic_socket(socket_int s, const sockaddress& addr);
    explicit basic_socket(const char_type* host);
    explicit basic_socket(const string_type& host);
    virtual ~basic_socket();

    basic_socket& connect(const char_type* host);
    basic_socket& connect(const string_type& host);

    int send_to(const char_type* src, int n, const sockaddress& addr);
    int send_to(const string_type& src, const sockaddress& addr);
    int send_to(const char_type* src, int n, const char_type* host);
    int send_to(const string_type& src, const string_type& host);
    int send(const char_type* src, int n);
    int send(const string_type& src);

    int recv(char_type* dest, int n);

    const sockaddress& from() const;
    const sockaddress& to() const;

send() メソッドは，コンストラクタ，または connect() メソッドで指定したホストに対して， 通信を試みます．from() メソッドは，直近の recv() メソッドを用いて受信したデータの送り先を返します．

# References

1. Life like a clown - ICMPパケットを利用したパケット間隔ベースの帯域計測方法
2. Geekなページ - 簡単なpingの作成（ICMPの送受信）
3. Winsock Programmer's FAQ - Ping: Raw Sockets Method