ping の作成

はじめに

Raw Socket (生ソケット) を利用すると Ping のようなプログラムを書くことも容易にできる。本稿では、単純な ping のサンプルコードを示す。通常の ping にはいろいろなオプションがあるが、ここでは最も単純なコードにした。

ICMP ヘッダ

Ping の実体は ICMP の Echo 要求である。

ここでは ICMP プロトコルの詳細は説明しない。今回のサンプルコードを読むにあたり、必要な程度のヘッダ情報を書き記すにとどめる。

詳細は RFC 792, 950, 1256 及び 1122 を参照のこと







+---------------+--------------+----------------------+
| 8 bit ICMP    |  8 bit ICMP  | 16 bit ICMP Checksum |
|      Type     |    Code      |                      |
+---------------+--------------+----------------------+
|                                                     |
|        ICMP Contents (depends on type and code)     |
|                                                     |
+-----------------------------------------------------+

ICMP ECHO の場合 ICMP タイプは 8。ICMP コードは 0。それからチェックサムが 16 ビットで続く (計算はコードを参照のこと)。ICMP Contents は次の形。

+------------------------------+-----------------------------+
|               ID             |          sequence           |
|                              |                             |
+------------------------------+-----------------------------+
|                     32 bit time stamp                      |
|                                                            |
+------------------------------------------------------------+

ID にはプロセスIDを入れる。シーケンスには 0。そしてタイムスタンプには GetTickCount を入れる。

上記のデータをターゲットに投げつけ、応答を待てばよい。

Ping の作成

 Ping プログラムサンプル [myping.zip, makefile プロジェクト]
※出力部分をライブラリにしてあります。printutils.lib をリンクしてください。

#include <winsock2.h>
#include <stdio.h>
#include "pingex.h"
#include "printutils.h"

#define ICMP_DATASIZE  (32)

ICMP エコー パケットの作成。pBuff として渡したバッファ (char* 型) にヘッダ情報を書き込む。

BOOL BuildICMPEchoPacket(char* pBuff) {
 
    static char chData = 'a';
    PICMP_HDR pICMPHdr;
    char* pDataTop;
 
    pICMPHdr = (PICMP_HDR) pBuff;
    pICMPHdr->icmp_type = 8; // echo request.
    pICMPHdr->icmp_code = 0;
    pICMPHdr->icmp_id = (unsigned short) GetCurrentProcessId();
    pICMPHdr->icmp_checksum = 0; // zero field before computing checksum
    pICMPHdr->icmp_sequence = 0;
 
    pDataTop = pBuff;
    pDataTop += sizeof(ICMP_HDR);
    memset(pDataTop, chData++, ICMP_DATASIZE);

    pICMPHdr->icmp_timestamp = GetTickCount();

    pICMPHdr->icmp_checksum = 
        CheckSum((USHORT*) pICMPHdr, sizeof(ICMP_HDR) + ICMP_DATASIZE);

    return TRUE;
 
}

main 関数はこちら。

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

    WSADATA wsaData;
    SOCKET s;
    SOCKADDR_IN AddrFrom, TargetAddr;
    char szBuff[sizeof(ICMP_HDR) + ICMP_DATASIZE], szBuffRecv[256];
    char* pHdrTop = NULL, szHostName[64] = "";
    INT nRecv, nRet, i;
    PIP_HEADER ip_hdr;
    PICMP_HDR icmp_header;
    struct timeval Timeout;
    fd_set readfds;
    DWORD dwTimeTaken;
    USHORT cs;
    ....

WinSock の初期化。

    ZeroMemory(&wsaData, sizeof(WSADATA));
    WSAStartup(MAKEWORD(2, 2), &wsaData);

続いて名前解決。 名前解決については、以前記事を書いているのでそ ちらを参照のこと。

    if(!ResolveAddress (argv[1], &TargetAddr, szHostName, 64)) {
        return;
    }

    // Startup Message.
    printf("Pinging %s [%s] with %d bytes of data:\n\n",
        szHostName, inet_ntoa(TargetAddr.sin_addr), ICMP_DATASIZE);

    for( i=0; i<4; i++) {

生ソケット (raw socket) を作成する。

        s = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);

select のための準備。

        readfds.fd_count = 1;
        readfds.fd_array[0] = s;
        Timeout.tv_sec = 5; // timeout 5 sec.
        Timeout.tv_usec  =0;

ICMP Echo パケットの作成。

        ZeroMemory (szBuff, sizeof(szBuff));
        BuildICMPEchoPacket(szBuff);

パケットの送信。

        nRet = sendto
            s, 
            szBuff, 
            sizeof(ICMP_HDR) + ICMP_DATASIZE, 
            0, 
            (SOCKADDR*) &TargetAddr, 
            sizeof(TargetAddr));

タイムアウト 5 秒で読み込み可能になるのを待つ。

        nRet = select(1, &readfds, NULL, NULL, &Timeout);

        if(nRet) {

受け取ったデータをバッファに読み込む。

            nRecv = sizeof(AddrFrom);
            nRet = recvfrom ( s, szBuffRecv, sizeof(szBuffRecv), 0, (SOCKADDR*)&AddrFrom, &nRecv);

            if( 0 == nRet || SOCKET_ERROR == nRet ) {
                printf("recvfrom Failed. (ret, gle) = (%d, %d)\n", nRet, WSAGetLastError());
                break;
            }
   
            dwTimeTaken = GetTickCount();

読み込んだデータの解析。

            ip_hdr = (PIP_HEADER) szBuffRecv;

            pHdrTop = szBuffRecv;
            pHdrTop += ((int) ip_hdr->ip_hl) * 4;
            icmp_header = (PICMP_HDR) pHdrTop;

            dwTimeTaken -= (DWORD) icmp_header->icmp_timestamp;

            cs = CheckSum((USHORT*)icmp_header, sizeof(ICMP_HDR)+ICMP_DATASIZE);

ステータスの表示。

            if(0 == cs) {
                printf("Reply from %s: bytes=%d time=%dms TTL=%d\n", 
                    inet_ntoa(ip_hdr->ip_src), 
                    nRet - ((int) ip_hdr->ip_hl) * 4 - sizeof(ICMP_HDR),
                    dwTimeTaken,
                    ip_hdr->ip_ttl);

                PrintHexDump(sizeof(ICMP_HDR), (PBYTE) icmp_header);
    
            }
            else {
                printf("受け取ったデータは破損しています。\n");
            }

        }
        else {

こちらはタイムアウトの場合。

            if(0 == nRet) { // Timed out.
                printf("Request timed out.\n");
            }
            else { // Other Errors
                printf("select Failed. %d\n", WSAGetLastError());
            }
        }

ソケットを閉じる。

        closesocket(s);

一秒だけ待ってから次のエコーパケットを送信する。

        Sleep(1000);
  
    }

WinSock のクリーンアップ。

    WSACleanup();

}

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 Web/DB プログラミング徹底解説