Python3で Pingを実装してみる。

初めに

こんばんは、突然Pythonの記事を書き始めようと思い、

たまたま2011 アドベントカレンダーを発見し無謀にも参加表明。

実はPython3を触るのは今回は始めて。かなり苦戦しました。

苦戦した点は後にまとめるとして、まず何をつくったのか。

Warning

※今回のソースは、Python3限定です! Python2ですと所々エラーが出るので、各自悩んで直してくださいw

Pingとは、何か

Pingとは、サーバーが生きているかの確認や、今ネットワークと繋がっているかを確認するためによく使うコマンド。

その実態は、ICMPと言うプロトコルを使い、PING通信を行っている。

ICMPは、IPなど処理を行うネットワーク層に存在し、よく聞くHTTPやFTPなどのアプリケーション層よりもずっと下の階層に存在する。

階層が下にあると言う事は、本来は知らなくよい話ではあるが、ここは一つマニアックに話を進めようと思う。

なお、この先、ビットの話やバイトの話が飛び交うので、今まで文字列しか触ってない人には厳しい話かもしれない。

ICMPプロトコルの作り方

ICMPは、EtherNetヘッダ と IPヘッダ と ICMPヘッダ の三種類のヘッダで構成されており、

EtherNetヘッダ と IPヘッダ はSocket関数が自動的に処理を行ってくれるため、

今回はICMPヘッダの作成を行いたいと思う。

細かなICMPのプロトコルは参照文献に詳しく乗っているが、ここでは簡単に説明する。

ICMPには以下の値を使い通信を行う。

1、タイプ(8ビット):ICMPメッセージのタイプを決めます。0x08ならPING要求 0x00ならPING応答

2、コード(8ビット):1のタイプにより変化。得に到着不能時の処理に使う

3、チェックサム(16ビット):メッセージ全体のチェックサムを行う

4、識別子(16ビット): よくわからないが、得に重要じゃない気がする。

5、シーケンスナンバー(16ビット): よくわからないが、得に(ry

6、データ(任意):エコーを行うデータ

と、こんな感じのデータを順番に送信していけばOKなはず。

Pythonソースコード

 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
#!/usr/bin/env python
#-*- coding:utf-8 -*-

import socket
#=============================================#
sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)

#=============================================#
IPaddr = "192.168.0.1"

ICM_Type = (0x08)
ICM_Code = (0x00)
ICM_ID = (0x01)
ICM_Seq = (0x01)
#=============================================#
ICM_LIST = [0]*6

ICM_LIST[0] = ICM_Type
ICM_LIST[1] = ICM_Code
ICM_LIST[2] = 0#ICM_ChekeSum1
ICM_LIST[3] = 0#ICM_ChekeSum2
ICM_LIST[4] = ICM_ID
ICM_LIST[5] = ICM_Seq
#=============================================#
ICM_DATA = b"abcdefghijklmnopqrstuvwxyz"
ICM_LIST.extend(ICM_DATA)
#=============================================#
csum = 0
for i in range(int(len(ICM_LIST)/2)):
	csum += (ICM_LIST[i*2]<<8) | (ICM_LIST[i*2+1])
csum = (csum&0xffff) + (csum>>16)
csum = 0xffff-(csum)

print("ChekeSum:"+hex(csum))
#=============================================#
ICM_LIST[2] = (csum&0xFF00)>>8        #ICM_ChekeSum2
ICM_LIST[3] = csum&0x00FF     #ICM_ChekeSum1
#=============================================#
print(bytes(ICM_LIST))

sock.sendto(bytes(ICM_LIST), (IPaddr, 0))
#=============================================#
data = sock.recv(255)

for i in range(20,len(data)):
    print ("%r" % hex(data[i])),
#=============================================#

Warning

※Python3で実行してください。

Warning

※Linux(Ubuntu)で動作確認してます。実行する際に sudo python が必要でした。なぜか。

解説

そんな感じで、サクッと実装してみた。

  1. まず、今回のPINGには Socketを使います。

http://docs.python.org/py3k/library/socket.html?highlight=socket#socket

  1. socketをSOCK_RAWとIPPROTO_ICMPの設定で開く
sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)

※なぜここで、SOCK_RAWとIPPROTO_ICMPを選択するかは、C言語のSocket関数を調べると凄く分かるのでお勧めw

3. PING送信先のIPを設定し、ICMヘッダへ代入する値(ここでは決め打ち)を代入する。

IPaddr = "192.168.0.1"
ICM_Type = (0x08)
ICM_Code = (0x00)
ICM_ID = (0x01)
ICM_Seq = (0x01)

4.ICMヘッダのリストを作成する。

ICM_LIST = [0]*6
ICM_LIST[0] = ICM_Type
ICM_LIST[1] = ICM_Code
ICM_LIST[2] = 0#ICM_ChekeSum1
ICM_LIST[3] = 0#ICM_ChekeSum2
ICM_LIST[4] = ICM_ID
ICM_LIST[5] = ICM_Seq

5. エコを行うデータをリストの最後に追加する。

ICM_DATA = b"abcdefghijklmnopqrstuvwxyz"
ICM_LIST.extend(ICM_DATA)

6.ヘッダ全体のチェックサムを行う。

csum = 0
for i in range(int(len(ICM_LIST)/2)):
        csum += (ICM_LIST[i*2]<<8) | (ICM_LIST[i*2+1])
csum = (csum&0xffff) + (csum>>16)
csum = 0xffff-(csum)

print("ChekeSum:"+hex(csum))

ICM_LIST[2] = (csum&0xFF00)>>8        #ICM_ChekeSum2
ICM_LIST[3] = csum&0x00FF     #ICM_ChekeSum1

7.作ったリストの中身を表示し、Socketのsendtoで送信する。この際ポートは0を設定する。

print(bytes(ICM_LIST))

sock.sendto(bytes(ICM_LIST), (IPaddr, 0))

8.帰ってきたデータを受け取り、一つずつ表示する。

data = sock.recv(255)

for i in range(20,len(data)):
        print ("%r" % hex(data[i]))

この際、受信したリスト20番より先にICMPのエコー内容が記載されている。 20番よりも前には受信先のIPなどが入っている。好きな人は解析してみるとよいかも。

終わりに

以上でPING送信ができます。

ものすごくザックリですみません。

ほんと、今回のPING実装は結構難しかった!

Python3のByteの扱い方がちょっと複雑で、しょっちゅう型が違う!って叱られました。

まぁ、なれてくると、エラーメッセージが何を怒っているか分かって来たのでよかったですが、

慣れるまで厳しそうですね。得に変換関数に関しては一度まとめたいと思います。

今回、こんな形でPython3アドベントカレンダーの13日めに参加出来てよかったです。

こんなPINGを送るだけの関数は得に需要が無いんじゃないかなと・・・

ネタバレをしてしまうと、得にPythonで実現しなくても、OSのコマンドを叩けばすむ話でw

そこをなぜ実装してしまったかは、まぁ勉強と思ってw

気持ち、もう少しこのPING関数を作り込めば、それなりな高性能PINGマシンが出来上がるんじゃないかなと思ったりもします。

と、言うことでw

Python3アドベントカレンダー13日目はギリギリ終了です!

次は14日目 @Surgoさんです!

クリエイティブ・コモンズ・ライセンス