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();
}