12-5 AF_PACKETを利用したICMP ECHO


#include <stdio.h> 
#include <string.h> 
#include <stdlib.h> 
#include <unistd.h> 
#include <sys/socket.h> 
#include <netinet/in.h> 
#include <arpa/inet.h> 
#include <netpacket/packet.h> 
#include <net/ethernet.h> 
#include <net/if.h> 
#include <net/if_arp.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 sockaddr_ll addr; 
  int n;  char buf[2000]; 
  struct icmphdr *icmphdrptr; 
  struct iphdr *iphdrptr; 
  if (argc != 4) { 
    printf("usage : %s srcaddr dstaddr ifname\n", argv[0]); 
    return 1; 
  } 
  /* AF_PACKETソケットを作成します */ 
  sock = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_IP)); 
  if (sock < 0) { 
    perror("socket"); 
    return 1; 
  } 
  /* IPヘッダとICMPヘッダのポインタをセットします */ 
  iphdrptr = (struct iphdr *)buf; 
  icmphdrptr = (struct icmphdr *)(buf + sizeof(struct iphdr)); 
  memset(&buf, 0, sizeof(buf)); 
  /* IPヘッダ情報を設定しています */ 
  iphdrptr->ttl = 32; // TTLは32 
  iphdrptr->protocol = IPPROTO_ICMP; // ICMP 
  iphdrptr->ihl = sizeof(struct iphdr) / 4;  // IP header length 
  iphdrptr->version = 4; // IP version 4 
  iphdrptr->tot_len = htons(sizeof(struct iphdr) + sizeof(struct icmphdr)); 
  iphdrptr->id = random(); // このサンプルではsrandom()をしてないので注意 
  inet_pton(AF_INET, argv[1], &iphdrptr->saddr); // source address 
  inet_pton(AF_INET, argv[2], &iphdrptr->daddr); // destination address 
  /* IPヘッダのチェックサムを計算します */ 
  iphdrptr->check = checksum((unsigned short *)iphdrptr, sizeof(struct 
iphdr)); 
  /* ICMPヘッダを用意します */ 
  icmphdrptr->type = ICMP_ECHO; 
  icmphdrptr->code = 0; 
  icmphdrptr->checksum = 0; 
  icmphdrptr->un.echo.id = 0; 
  icmphdrptr->un.echo.sequence = 0; 
  /* ICMPヘッダのチェックサムを計算します */ 
  icmphdrptr->checksum = checksum((unsigned short *)icmphdrptr, sizeof(struct 
icmphdr)); 
  /* 宛先用のsockaddr_llを設定します */ 
  memset(&addr, 0, sizeof(addr)); 
  /* 送信用のsockaddr_llを設定しています */ 
  addr.sll_family = AF_PACKET; 

  addr.sll_protocol = htons(ETH_P_IP); //IPv4です。IPv6はETH_P_IPV6です 
  addr.sll_ifindex = if_nametoindex(argv[3]); 
  addr.sll_halen = IFHWADDRLEN; // IFHWADDRLEN => 6
  memset(&addr.sll_addr, 0xff, IFHWADDRLEN); // 面倒なのでbroadcast

  /* ICMPヘッダだけのICMPパケットを送信します */ 
  /* ICMPデータ部分はプログラムを簡潔にするために省いています */ 
  n = sendto(sock, 
             (char *)iphdrptr, sizeof(struct iphdr) + sizeof(struct icmphdr), 
             0, (struct sockaddr *)&addr, sizeof(addr)); 
  if (n < 1) { 
    perror("sendto"); 
    return 1; 
  } 
  /* ICMP送信部分はここまでです*/ 
  /* ここから下はICMP ECHO REPLY受信部分になります */ 
  memset(buf, 0, sizeof(buf)); 
  /* 相手ホストからのICMP ECHO REPLYを待ちます */ 
  n = recv(sock, buf, sizeof(buf), 0); 
  if (n < 1) { 
    perror("recv"); 
    return 1; 
  } 
  /* 受信データから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; 
}

拙著「Linuxネットワークプログラミング」の紹介

プロフェッショナルIPv6解説動画シリーズ再生リスト

動画で学ぶ「プロフェッショナルIPv6」を作っています。 もしよろしければご覧ください。お楽しみいただければ幸いです!