pythonで簡単なpingを作ってみる
RFC792にICMPの仕様が書いてあります。
RFCによるとpingはEcho MessageとEcho Reply Messageの2つを使用しているっぽいです。
このようなパケットの構成になっていて、タイプに8をセットすると
Echo Message、0をセットするとEcho Reply Messageになるようです。
Cのサンプルソースや色々インターネットに出回っていますが、
ここではシンプルに手で作成してみます。
まず、タイプは0x08か0x00です。
コードは使用していないようなので0x00です。
チェックサムはRFCを読んだりしていても、いまいち理解できず、
ここを参考にしました。
http://d.hatena.ne.jp/kingkunikuni/20100116/1263631169
ICMPパケット全体を16bitずつで積算して、桁上りが発生したら、
溢れた分を再度足す。最後にbitを反転さして出来上がり。
ちなみにチェックサムの計算中はチェックサムの部分は0として計算する。
具体例は、後ほどパケットを作成するときに書きます。
識別子とシーケンス番号はパケットをEchoとEcho Replyを結び付けやすくするための番号みたいなもので、ゼロでもいいみたいです。
データですが、これまたなんでもよさそうな感じです。
通常のpingを打ってみると、
abcdefghijk...とか、asciiコードにすると61626364のようになっていたり
(これはwindowsからのping)
ubuntuからだとasciiコードで10111213というようになっています。
pingのサイズによってこれがずらーっとつらなっているわけです。
これらを踏まえてping用のパケットを作ってみたいと思います。
0800 0000 0101 0102
まずは、Echo Messageを相手に送るため08
コードは00
チェックサムは最初は0000
にしておきます。
IDとSeqNoは両方とも1にして0101
データは0102とします。
それではチェックサムを計算してみます。
以下は16進数で計算しています。
0800 + 0000 + 0101 + 0102 = A03
NOT A03はF5FCです。
桁上りがある場合は、こんな感じになります。
0800 + 0000 + 0101 + AFAF + CDCD = 1867D
1溢れるので
867D + 1 = 867E
NOT 867E = 7981
さてこれでpingのパケットは完成です。
0800 F5FC 0101 0102
これだけIPヘッダにくっ付けて送ってやればいいのです。
sping.py
import socket sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP) sock.sendto('\x08\x00\xf5\xfc\x01\x01\x01\x02', ('150.13.16.184', 0))
とりあえず送ってみます。
rootでターミナルをひとつ別に立ちあげて、パケットを拾ってみます。
# tcpdump -vvX proto ICMP
この状態で別のターミナルからrootで先程のpythonスクリプトを実行します。
# python sping.py
ちなみにpingはrootでないと打てません。
普通のpingコマンドが通常ユーザーで実行できるのはsetuid権限が設定されているからです。
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes 15:10:00.685187 IP (tos 0x0, ttl 128, id 21524, offset 0, flags [none], proto ICMP (1), length 28) hoge.c o.jp > hoge.local: ICMP echo request, id 257, seq 258, length 8 0x0000: 4500 001c 5414 0000 8001 992d 960d 10cd E...T......-.... 0x0010: 960d 10b8 0800 f5fc 0101 0102 0000 0000 ................ 0x0020: 0000 0000 0000 0000 0000 0000 0000 .............. 15:10:00.685234 IP (tos 0x0, ttl 64, id 50147, offset 0, flags [none], proto ICMP (1), length 28) hoge.local > hoge. co.jp: ICMP echo reply, id 257, seq 258, length 8 0x0000: 4500 001c c3e3 0000 4001 695e 960d 10b8 E.......@.i^.... 0x0010: 960d 10cd 0000 fdfc 0101 0102 ............
0800 f5fc 0101 0102
というデータが届きました。
その後Echo Replyを返しています。
次のようにスクリプトを変更してEcho Replyも受信します。
sping.py
import socket sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP) sock.sendto('\x08\x00\xf5\xfc\x01\x01\x01\x02', ('150.13.16.184', 0)) data = sock.recv(255) print 'recv Data' for i in data: print '%r' % hex(ord(i)),
動かしてみます。
# python sping.py recv Data '0x45' '0x0' '0x0' '0x1c' '0xc3' '0xe3' '0x0' '0x0' '0x40' '0x1' '0x69' '0x5e' '0x96' '0xd' '0x10' '0xb8' '0x96' '0xd' ' 0x10' '0xcd' '0x0' '0x0' '0xfd' '0xfc' '0x1' '0x1' '0x1' '0x2'
こんな感じでpingが作れました。