Euniclus

カテゴリー: c言語

IPv6ネットワークプログラミング NDP(近隣広告)編

IPv4とIPv6には互換性がなくパケットの構造も全く違っています。そのためIP […]

2020年1月11日2020年3月2日 c言語、ネットワーク、プログラミング

IPv6ネットワークプログラミング ICMPV6編

ICMPV6を使ったプログラムを作ります。作るものは以下の記事に書いたようなtr […]

2019年12月2日2020年2月25日 c言語、ネットワーク、プログラミング

c言語でGMPを使って多倍長演算

c言語で標準では多倍長整数がサポートされていないのでこれらを扱いたい場合は自分で […]

2019年11月8日2020年2月25日 c言語、プログラミング

c言語で文字列を区切り文字で配列に

c言語では文字列の扱いが面倒です。他の言語にあるような文字列を処理する便利な関数 […]

2019年10月25日2020年2月25日 c言語

xv6の力を借りてPCIデバイスの情報を取得しよう

コンピュータには周辺機器とやり取りするためにPCIという仕組みがあります。 OS […]

2019年10月5日2020年3月1日 c言語、OS、プログラミング

ICMPを使ったネットワークプログラム


今回はネットワークとプログラミング両方の学習に役立つ簡単なtracerouteを実装します。
ネットワークプログラミングのいい勉強になりますし、そこそこ格好いいのでいろいろと意味があると思います。

このツールを利用すると通信先マシンへの経路上にあるマシンのIPアドレスなどの情報を取得することができます。

原理をまとめそれを実装してパケットがどのようにネットワークを移動するのか見ていきます。



そもそもどのように対象のマシンへパケットが送られるか

僕も普段何気なくインターネットを利用していますが、
中を覗いてみるとそこそこ面倒なことをやっているようです。

インターネットのどこかのマシンと通信する際は直接そのマシンとやり取りしているわけではなく、ルーターを経由してパケットを送りあっています。

通信先との間にルーターがあるわけですが何台あるかはわかりません。
例えば5台あるとすれば5回ルーターを経由して対象のマシンへとパケットが送られます

time to live

しかしIPパケットにはtime to live(IPv6ではhop limit)という寿命のようなものがあります。
このtime to liveというのは簡単に言うとルーターを何台超えることができるかを定めた値です。

たとえば対象マシンとの間にルーターが5台あり、time to liveが3だとするとこのパケットは対象のマシンへと到達する前に破棄されてしまいます。
それはtime to liveが3であるため3回しかルーターを超えることができないからです。
ルーター(ルートのホスト)を通るたびにtime to liveはデクリメントされていきます。
そしてゼロになるとTimeExceededを知らせるICMPパケットが送信者に向けて返却されます。

ルートスキャンというものはこの原理を利用しています。

原理

time to liveがゼロになるとTime Exceededを知らせるICMPパケットが送信者に向けて返却されると書きました。

このICMPパケットにはICMPパケットを発行したルーターのIPアドレスが格納されています。
とういことはルート上にルーターが5台あるとして、
time to liveを1にすれば、自分のマシンの、一つ先のマシンの情報が取得できるということです。

一つ先のマシンからICMPパケットを受信したら、次はtime to liveを2に設定して、通信したい相手へとパケットを送信します。
そうすると2つ先のマシンがICMPパケットを返却してきます。(time to liveが2だから)
そして2つ先のマシンの情報も取得できます。

これを最終到達地点のマシンへパケットが届くまで、time to liveをインクリメントして繰り返すのです。

こうすることで、ルート上のマシンの情報が取得できるということです。

実装


冷静になって実装します。

まずはソケットを作成する部分です。
今回はicmpパケットを送受信します。
特にこれといったことはやっていません。
ソケットの作成に成功したらディスクリプタを、失敗したら-1を返却します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// ICMPV ソケット作成
int make_socket() {
  int soc;
 
  if ( (soc = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) == -1) {
    perror("socket");
    return -1;
  }
 
  int opt = 1;
  if (setsockopt(soc, IPPROTO_IP, IP_HDRINCL, &opt, sizeof(opt)) == -1) {
    perror("setsockopt");
    close(soc);
    return -1;
  }
 
  return soc;
}
 


次は早速送受信です。

ipヘッダーとicmpヘッダーに諸々値を設定します。
今回はicmp echo requestパケットを送信するためicmp_typeにICMP_ECHOを設定します。

Time to liveが0になれば時間超過を知らせるtime exceededパケットが返却され、
ターゲットにパケットが到達すればecho replyが返却されます。

その解析をanalyze関数にお任せします。
echo replyが返却された場合は1、
time exceededが返却された場合0、
それ以外は一応2を返却します。

analyze関数から「1」が返却された場合は最終目標に到達したわけですから、
パケット送信ループを抜けます。
「0」の場合はTime to liveをインクリメントして送信処理を続行します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
 
// パケット送受信
void send_recv_packet(int soc, char *dst) {
  // 受信パケット解析用
  struct ip *ip;
  struct icmp *icmp;
 
  struct sockaddr_in sock_in;
  memset(&sock_in, 0, sizeof(sock_in));
 
  struct ip_icmp ip_icmp;
  memset(&ip_icmp, 0, sizeof(ip_icmp));
 
  // パケット設定
  ip_icmp.ip.ip_v = 4;
  ip_icmp.ip.ip_hl = 5;
  ip_icmp.ip.ip_id = htons(1);
  ip_icmp.ip.ip_off = 0;
  ip_icmp.ip.ip_len = htons(sizeof(struct ip_icmp));
  ip_icmp.ip.ip_p = IPPROTO_ICMP;
  inet_pton(AF_INET, dst, &ip_icmp.ip.ip_dst);
 
  ip_icmp.icmp.icmp_type = ICMP_ECHO;
  ip_icmp.icmp.icmp_code = 0;
  ip_icmp.icmp.icmp_cksum = 0;
  ip_icmp.icmp.icmp_id = 1;
  ip_icmp.icmp.icmp_seq = 1;
  ip_icmp.icmp.icmp_cksum = checksum((uint16_t *)&ip_icmp.icmp,
                                                 sizeof(ip_icmp.icmp));
 
  // アドレス情報
  sock_in.sin_family = AF_INET;
  inet_pton(AF_INET, dst, &sock_in.sin_addr);
 
  int reach = 0;
 
  // 一応255まで
  for (int i=1; i<256; i++) {
    char res[512], *p;
 
    ip_icmp.ip.ip_ttl = i;
    // 3回
    for (int j=0; j<3; j++) {
 
      fd_set ready;
      struct timeval tv;
 
      FD_ZERO(&ready);
      FD_SET(soc, &ready);
 
      tv.tv_sec = 3;
      tv.tv_usec = 0;
 
      int r = sendto(
                  soc,
                  (char *)&ip_icmp,
                  sizeof(ip_icmp),
                  0,
                  (struct sockaddr *)&sock_in,
                  sizeof(struct sockaddr_in)
                );
      if (r == -1) {
        perror("sendto");
        return;
      }
 
      int sel_res = select( (soc+1), (fd_set *)&ready, NULL, NULL, &tv);
      if (sel_res == 0) {
        if (j == 0) {
          printf("- Time to live %d ? ", i);
        } else if (j == 2) {
          printf("?\n");
        } else {
          printf("? ");
        }
        fflush(stdout);
      } else if (sel_res == -1){
 
      } else if (sel_res > 0) {
        if (FD_ISSET(soc, &ready)) {
          if (recvfrom(soc, res, sizeof(res), 0, NULL, NULL) == -1) {
            perror("recvfrom");
          }
 
          p = res;
          ip = (struct ip *)p;
          p += sizeof(struct ip);
          if (ip->ip_p == IPPROTO_ICMP) {
            char ip_addr[128];
            icmp = (struct icmp *)p;
            int result = analyze(icmp);
            inet_ntop(AF_INET, &ip->ip_src, ip_addr, sizeof(ip_addr));
            if (result == 1) {
              printf("- Reach Time to live %d from %s\n",i, ip_addr);
              reach = 1;
              break;
            } else if (result == 0) {
              printf("- Time to live %d from %s\n",i, ip_addr);
              break;
            }
          }
        }
      }
 
    }
    if (reach == 1) {
      break;
    }
  }
}
 

ソースコード


最終的にこのようなプログラムになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
#include <sys/socket.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
 
enum {
  COMMAND, DST
};
 
struct ip_icmp {
  struct ip ip;
  struct icmp icmp;
};
 
int analyze(struct icmp *icmp);
uint16_t checksum(uint16_t *data, int size);
int make_socket();
void send_recv_packet(int soc, char *dst);
 
uint16_t checksum(uint16_t *data, int size) {
  uint32_t sum = 0;
  for (;size > 1; size -= 2) {
    sum += *data++;
    if (sum & 0x80000000) {
      sum = (sum >> 16) + (sum & 0xffff);
    }
  }
 
  if (size == 1) {
    uint8_t odd;
    odd = *(uint8_t *)data;
    sum += odd;
  }
 
  while(sum >> 16) {
    sum = (sum >> 16) + (sum & 0xffff);
  }
 
  return ~sum;
}
 
// パケット送受信
void send_recv_packet(int soc, char *dst) {
  // 受信パケット解析用
  struct ip *ip;
  struct icmp *icmp;
 
  struct sockaddr_in sock_in;
  memset(&sock_in, 0, sizeof(sock_in));
 
  struct ip_icmp ip_icmp;
  memset(&ip_icmp, 0, sizeof(ip_icmp));
 
  // パケット設定
  ip_icmp.ip.ip_v = 4;
  ip_icmp.ip.ip_hl = 5;
  ip_icmp.ip.ip_id = htons(1);
  ip_icmp.ip.ip_off = 0;
  ip_icmp.ip.ip_len = htons(sizeof(struct ip_icmp));
  ip_icmp.ip.ip_p = IPPROTO_ICMP;
  inet_pton(AF_INET, dst, &ip_icmp.ip.ip_dst);
 
  ip_icmp.icmp.icmp_type = ICMP_ECHO;
  ip_icmp.icmp.icmp_code = 0;
  ip_icmp.icmp.icmp_cksum = 0;
  ip_icmp.icmp.icmp_id = 1;
  ip_icmp.icmp.icmp_seq = 1;
  ip_icmp.icmp.icmp_cksum = checksum((uint16_t *)&ip_icmp.icmp,
                                                 sizeof(ip_icmp.icmp));
 
  // アドレス情報
  sock_in.sin_family = AF_INET;
  inet_pton(AF_INET, dst, &sock_in.sin_addr);
 
  int reach = 0;
 
  // 一応255まで
  for (int i=1; i<256; i++) {
    char res[512], *p;
 
    ip_icmp.ip.ip_ttl = i;
    // 3回
    for (int j=0; j<3; j++) {
 
      fd_set ready;
      struct timeval tv;
 
      FD_ZERO(&ready);
      FD_SET(soc, &ready);
 
      tv.tv_sec = 3;
      tv.tv_usec = 0;
 
      int r = sendto(
                  soc,
                  (char *)&ip_icmp,
                  sizeof(ip_icmp),
                  0,
                  (struct sockaddr *)&sock_in,
                  sizeof(struct sockaddr_in)
                );
      if (r == -1) {
        perror("sendto");
        return;
      }
 
      int sel_res = select( (soc+1), (fd_set *)&ready, NULL, NULL, &tv);
      if (sel_res == 0) {
        if (j == 0) {
          printf("- Time to live %d ? ", i);
        } else if (j == 2) {
          printf("?\n");
        } else {
          printf("? ");
        }
        fflush(stdout);
      } else if (sel_res == -1){
 
      } else if (sel_res > 0) {
        if (FD_ISSET(soc, &ready)) {
          if (recvfrom(soc, res, sizeof(res), 0, NULL, NULL) == -1) {
            perror("recvfrom");
          }
 
          // 一応パケットを調べる
          p = res;
          ip = (struct ip *)p;
          p += sizeof(struct ip);
          if (ip->ip_p == IPPROTO_ICMP) {
            char ip_addr[128];
            icmp = (struct icmp *)p;
            int result = analyze(icmp);
            inet_ntop(AF_INET, &ip->ip_src, ip_addr, sizeof(ip_addr));
            if (result == 1) {
              printf("- Reach Time to live %d from %s\n",i, ip_addr);
              reach = 1;
              break;
            } else if (result == 0) {
              printf("- Time to live %d from %s\n",i, ip_addr);
              break;
            }
          }
        }
      }
 
    }
    if (reach == 1) {
      break;
    }
  }
}
 
int analyze(struct icmp *icmp) {
  switch(icmp->icmp_type) {
    case ICMP_ECHOREPLY:
      return 1;
      break;
    case ICMP_TIME_EXCEEDED:
      if (icmp->icmp_code == ICMP_EXC_TTL) {
        return 0;
      }
      break;
    default:
      printf("%d\n", icmp->icmp_type);
  }
 
  return 2;
}
 
// ICMPV ソケット作成
int make_socket() {
  int soc;
 
  if ( (soc = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) == -1) {
    perror("socket");
    return -1;
  }
 
  int opt = 1;
  if (setsockopt(soc, IPPROTO_IP, IP_HDRINCL, &opt, sizeof(opt)) == -1) {
    perror("setsockopt");
    close(soc);
    return -1;
  }
 
  return soc;
}
 
 
int main(int argc, char *argv[]) {
 
  if (argc < 2) {
    printf("Arg [Destination IP Address]\n");
    exit(1);
  }
 
  int soc;
  if ((soc = make_socket()) == -1) {
    printf("[*] Error in make_socket\n");
    exit(1);
  }
 
  send_recv_packet(soc, argv[DST]);
  return 0;
}
 


2019年4月18日2020年2月25日 c言語、ネットワーク、プログラミング

【c言語】ARPスプーフィングで通信を傍受する

先日のARPポイズニングの記事を書きました。 対象にARPパケットを送りARPテ […]

2019年4月5日2022年6月25日 c言語、セキュリティー、ネットワーク、プログラミング

【c言語】ARPポイズニングの解説と実装

ARP poisoningの仕組みの簡単な説明とプログラムをまとめています。

2019年3月31日2022年7月2日 c言語、セキュリティー、ネットワーク、プログラミング

免責事項

カテゴリー

  • android (11)
  • C# (1)
  • c++ (1)
  • c言語 (8)
  • JavaScript (2)
  • Julia (2)
  • Kotlin (11)
  • OS (1)
  • Python (14)
  • Ruby (3)
  • Ruby on Rails (2)
  • Rust (9)
  • Scala (4)
  • Unity (1)
  • その他 (6)
  • セキュリティー (4)
  • ネットワーク (16)
  • フレームワーク (3)
  • プログラミング (35)
  • 暗号 (5)
  • 機械学習 (3)

最近の投稿

C#, Unity

【Unity×C#】【エディタカスタム】自動でシーンにオブジェクトを配置する

android, Kotlin

【Kotlin×Android×SQLite】データベースを使いメモアプリを作る

android, Kotlin

【Kotlin×Android】リストビューのレイアウトカスタマイズ

android, Kotlin

【Kotlin×Android】ファイルから情報を読み込む

android, Kotlin

【Kotlin×Android】ファイルに情報を保存する方法

c++

【c++】クラスの基礎と使い方

android, Kotlin

【Kotlin×Android】配列のリスト表示からイベント処理まで

Rust

【Rust】文字列関数まとめ