Euniclus

月: 2019年4月

Ruby on Railsの環境構築

Ruby on Railsについて記事を書いていきたいと思います。理由は前からr […]

2019年4月30日2020年1月23日 Ruby on Rails、フレームワーク

少し前にドローンをハックした話

少し前にドローンに興味を持ちまして購入したことを思い出しました。そのドローンはA […]

2019年4月27日2019年6月22日 Python、セキュリティー、ドローン

相関係数

データサイエンスに必要な知識はいろいろあるようです。ITスキルはもちろんですが、 […]

2019年4月26日2020年2月25日 その他

プログラマーになるのであれば参考書選びは大切

プログラミングを独学で勉強する際に参考書選びはとても重要です。選んだ参考書によっ […]

2019年4月23日2019年6月22日 その他

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言語、ネットワーク、プログラミング

Rustで簡単なネットワークプログラムをつくる

少し前にRustに関する記事を書いたと思います。どうしても気になってしまいまして […]

2019年4月16日2020年2月25日 Rust、ネットワーク、プログラミング

Pythonで簡単なネットワークプログラムをつくる

今回はpythonによるネットワークプログラミングに触れていきたいと思います ネ […]

2019年4月14日2020年12月26日 Python、プログラミング

Pythonの使い方

Pythonという言語 機械学習エンジニアとかセキュリティーエンジニアがよく好ん […]

2019年4月14日2019年11月7日 Python、プログラミング

Rustという言語

Rustという言語をご存知でしょうか。Mozillaプロジェクトが開発している言 […]

2019年4月13日2019年4月24日 Rust、プログラミング

Pythonで公開鍵暗号を実装する

前回は公開鍵暗号について簡単にまとめました。今回はpythonで公開鍵暗号を実装 […]

2019年4月12日2020年2月25日 Python、プログラミング、暗号

投稿ナビゲーション

1 2

免責事項

アーカイブ

  • 2020年11月 (2)
  • 2020年10月 (1)
  • 2020年9月 (1)
  • 2020年3月 (6)
  • 2020年2月 (5)
  • 2020年1月 (3)
  • 2019年12月 (1)
  • 2019年11月 (2)
  • 2019年10月 (3)
  • 2019年9月 (2)
  • 2019年8月 (7)
  • 2019年7月 (7)
  • 2019年6月 (6)
  • 2019年5月 (6)
  • 2019年4月 (12)
  • 2019年3月 (1)

カテゴリー

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

最近の投稿

Rust

Rustの文字列関数

Rust

Rustでのファイル操作

JavaScript

手軽にwebサーバーを動かす方法

その他

【Blender2.8 SubstancePainter2020】blenderで出力したfbxをSubstancePainterに持っていく

Python, プログラミング

Pythonでnmapを使う方法

その他

Androidの省電力設定

その他

【紛失させた時などに役立つ】Androidでロック画面にメッセージを表示させる方法

その他

【Gmail】簡単で詳細な検索方法