簡単なpingの作成(ICMPの送受信)

ここでは、LinuxでRAWソケットを利用してICMPパケットの送受信を行う方法を説明したいと思います。

単純なpingプログラム

pingコマンドはICMP(Internet Control Message Protocol)ECHOメッセージを送信し、相手ホストからのICMP ECHO REPLYメッセージを受け取る事により実現しています。 ここでは、ICMP ECHOメッセージを送信し、ICMP ECHO REPLYメッセージを受信する方法を説明したいと思います。 ICMPメッセージの送受信を行うにはRAWソケットと呼ばれるソケットを作成する必要があります。 RAWソケットの作成にはroot権限が必要です。 下記サンプルプログラムをコンパイルするのは一般ユーザでも大丈夫ですが、実行はrootで行う必要があります。 「pingコマンドはrootじゃなくても出来るけど?」と思う方もいるかも知れませんが、それはpingコマンドがrootにsetuidしてあるからです。

pingのサンプルコードを以下に示します。


#include <stdio.h>

#include <sys/socket.h>
#include <netinet/in.h>

#include <netinet/ip.h>
#include <netinet/ip_icmp.h>

/*
 * チェックサムを計算する関数です。
 * ICMPヘッダのチェックサムフィールドを埋めるために利用します。
 * IPヘッダなどでも全く同じ計算を利用するので、
 * IPヘッダのチェックサム計算用としても利用できます。
 */
unsigned short
checksum(unsigned short *buf, int bufsz)
{
  unsigned long sum = 0;

  while (bufsz > 1) {
    sum += *buf;
    buf++;
    bufsz -= 2;
  }

  if (bufsz == 1) {
    sum += *(unsigned char *)buf;
  }

  sum = (sum & 0xffff) + (sum >> 16);
  sum = (sum & 0xffff) + (sum >> 16);

  return ~sum;
}

/* main 文はここからです。*/
int
main(int argc, char *argv[])
{
  int sock;
  struct icmphdr hdr;
  struct sockaddr_in addr;
  int n;

  char buf[2000];
  struct icmphdr *icmphdrptr;
  struct iphdr *iphdrptr;

  if (argc != 2) {
    printf("usage : %s IPADDR\n", argv[0]);
    return 1;
  }

  addr.sin_family = AF_INET;
  addr.sin_addr.s_addr = inet_addr(argv[1]);

  /* RAWソケットを作成します */
  sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
  if (sock < 0) {
    perror("socket");
    return 1;
  }

  memset(&hdr, 0, sizeof(hdr));

  /* ICMPヘッダを用意します */
  hdr.type = ICMP_ECHO;
  hdr.code = 0;
  hdr.checksum = 0;
  hdr.un.echo.id = 0;
  hdr.un.echo.sequence = 0;

  /* ICMPヘッダのチェックサムを計算します */
  hdr.checksum = checksum((unsigned short *)&hdr, sizeof(hdr));

  /* ICMPヘッダだけのICMPパケットを送信します */
  /* ICMPデータ部分はプログラムを簡潔にするために省いています */
  n = sendto(sock,
             (char *)&hdr, sizeof(hdr),
             0, (struct sockaddr *)&addr, sizeof(addr));
  if (n < 1) {
    perror("sendto");
  }

  /* ICMP送信部分はここまでです*/
  /* ここから下はICMP ECHO REPLY受信部分になります */

  memset(buf, 0, sizeof(buf));

  /* 相手ホストからのICMP ECHO REPLYを待ちます */
  n = recv(sock, buf, sizeof(buf), 0);
  if (n < 1) {
    perror("recv");
  }

  /* 受信データからIPヘッダ部分へのポインタを取得します */
  iphdrptr = (struct iphdr *)buf;

  /*
   * 本当はIPヘッダを調べて
   * パケットがICMPパケットかどうか調べるべきです
   */

  /* 受信データからICMPヘッダ部分へのポインタを取得します */
  icmphdrptr = (struct icmphdr *)(buf + (iphdrptr->ihl * 4));

  /* ICMPヘッダからICMPの種類を特定します */
  if (icmphdrptr->type == ICMP_ECHOREPLY) {
    printf("received ICMP ECHO REPLY\n");
  } else {
    printf("received ICMP %d\n", icmphdrptr->type);
  }

  /* 終了 */
  close(sock);

  return 0;
}

上記サンプルではかなり色々省略してありますが、最低限は動くと思います。 動作しない、よくわからない、説明が足りない、間違っているなどがあればご指摘頂ければ幸いです。