うさちゃんと一緒(5) ScapyでARPしてICMP、一歩づつ? Rabbit 4000

Joseph Halfmoon

ふと見ると「うさちゃん」ボードに薄っすらと埃が積もってました(うさちゃんの御近影は冒頭に。)前回から大分間が空いてしまいました。今回は前回の続きでARPパケットにお返事をもらい、リプライの中から必要な情報を取り出し、次なる送信パケットを組み立てたいと思います。

※Ethernetの通信の一方は、Raspberry Pi 3 model B+(Raspberry Pi OS機)上で、Scapy(Python上で任意のパケットを最下層から構築可能なモジュール)を使用して行っています。

※Ethernet通信のもう一方として、「最強のZ80後継機」Rabbit4000を使用しています。開発環境はDynamic C 10.72Eを使用しています。

リハビリ

前回から1か月以上も間が空いてしまったので、この老人はほぼ全て忘れています。まずは前回同様のところに戻るところから。

ステップ1 うさちゃんにソフト書き込んで起動

Dynamic Cを起動し、前回も使用した実験用のソフトウエアをうさちゃんボードに書き込み起動いたしました。DynamicCのコンソールに IP=192.168.2.59 として立ち上がって来たのを確認。うさちゃんはReady

ステップ2 ラズパイ上でScapyをインタラクティブ起動

前回同様 ScapyのインタラクティブなSHELL(?)を使用して実験してみるので以下のようにしてscapyを起動します。

sudo scapy -H

以下のグリーティングメッセージが出てまいりました。

Welcome to Scapy (2.4.5) using IPython 7.32.0

これでプロンプト >>> に対してコマンドを打ち込んでいけば実験できます。

ステップ3 ラズパイからRabbit 4000にARP問い合わせ

前回にもどるために、ラズパイからRabbit4000にARPの問い合わせパケットを投げつけてみます。こんな感じ。

ans, unans = srp(Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst="192.168.2.59"),timeout=2)

ARP(Address Resolution Protocol)を勝手な理解でかいつまむと、通信したい相手のIPアドレスが分かっているけれどMACアドレスが不明の相手に対して、MACアドレスを教えてもらうためのものです。今回、ラズパイからみるとうさちゃんは同じローカルなネットワーク上に存在しています。ラズパイからEthernetのブロードキャストアドレス(オール1)にARPパケットを投げると、うさちゃんは、自分のMACアドレスを書き込んだARPリプライを直接返してきます。こんな感じ。

Begin emission:
Finished sending 1 packets.

Received 1 packets, got 1 answers, remaining 0 packets
>>> ans.show()
0000 Ether / ARP who has 192.168.2.59 says 192.168.2.121 ==> Ether / ARP is at 00:90:c2:ce:68:a6 says 192.168.2.59 / Padding

前回は上記をみて、ちゃんとMACアドレス(期待どおり)が書き込まれたパケットが返ってきた、というところで終わっていました。しかし、返ってきたパケットから必要な部分を取り出して、次の送信パケットを組み立てて、また送信するということをしないと通信になりませぬ。

今回はその取り出し方を練習してみます。まず、上記のようなサマライズされたリプライパケットの「生」の中身を見てみました。

>>> ans[0]
QueryAnswer(query=<Ether dst=ff:ff:ff:ff:ff:ff type=ARP |<ARP pdst=192.168.2.59 |>>, answer=<Ether dst=b8:27:eb:89:e9:19 src=00:90:c2:ce:68:a6 type=ARP |<ARP hwtype=0x1 ptype=IPv4 hwlen=6 plen=4 op=is-at hwsrc=00:90:c2:ce:68:a6 psrc=192.168.2.59 hwdst=b8:27:eb:89:e9:19 pdst=192.168.2.121 |<Padding load='ssNR\x00\x00\x00\x00\x00\x00\x00\x00 h\\x9d\x06n(' |>>>)

上記をみて分かるのは、2つのパケットが組になって含まれていることです。

    • query 送信したARP問い合わせパケットの内容そのまま
    • answer 返信されてきたARPリプライパケットの内容

つまり answer の側から必要な情報を取り出さないといけないようです。answerの「型」を調べると以下のようでした。

>>> type(ans[0].answer)
scapy.layers.l2.Ether

Ethernetレベルのアドレスもここに含まれています。

>>> ans[0].answer.dst
'b8:27:eb:89:e9:19'
>>> ans[0].answer.src
'00:90:c2:ce:68:a6'

上記.srcには、Etherパケットレベルで送信元MACアドレスが含まれているのでこれで目的は果たしているのですが、一応、ARPリプライパケットの中のフィールドも見てみます。

>>> ans[0].answer.op
2

まず、op=2をしらべてみると「ARP要求への応答」ということであるので期待通りです。次に

>> ans[0].answer.hwsrc
'00:90:c2:ce:68:a6'
>>> ans[0].answer.psrc
'192.168.2.59'

上記のようにARPリプライ・パケット内にも送信元MACアドレスと送信元IPアドレスが見つかりました。

なお、ScapyのUsageのページに、必要な情報をカッコよく取り出すためのワンライナーがありました。こんな感じ。

>>> ans.summary(lambda s,r: r.sprintf("%Ether.src% %ARP.psrc%"))
00:90:c2:ce:68:a6 192.168.2.59

後で使用するために取り出せたMACアドレスとIPアドレスを変数に記憶させておきました。

>>> targetMAC=ans[0].answer.hwsrc
>>> targetIP=ans[0].answer.psrc
とりだした情報を元に普通のPing(ICMPパケット)作成して送信

実際には以下のようなメンドイことをしなくても(IPアドレス指定するだけで)ICMPパケットを送信することができます。しかし今回は折角ARPからMACアドレスを手作業で取り出したので、それを使ってことさら最下層から構築して送信してみます。こんな感じ。

>>> ans, unans = srp(Ether(dst=targetMAC)/IP(dst=targetIP)/ICMP())
Begin emission:
Finished sending 1 packets.

Received 2 packets, got 1 answers, remaining 0 packets
>>> ans
<Results: TCP:0 UDP:0 ICMP:1 Other:0>
>>> ans.summary()
Ether / IP / ICMP 192.168.2.121 > 192.168.2.59 echo-request 0 ==> Ether / IP / ICMP 192.168.2.59 > 192.168.2.121 echo-reply 0 / Padding

answerの中身を見てみると以下のようです。

>>> ans[0].answer
<Ether dst=b8:27:eb:89:e9:19 src=00:90:c2:ce:68:a6 type=IPv4 |<IP version=4 ihl=5 tos=0x0 len=28 id=1 flags= frag=0 ttl=64 proto=icmp chksum=0xf4db src=192.168.2.59 dst=192.168.2.121 |<ICMP type=echo-reply code=0 chksum=0xffff id=0x0 seq=0x0 |<Padding load='ssNR\x00\x00\x00\x00\x00\x00\x00\x00 h\\x9d\x06n(' |>>>>

Etherの中にIPがあり、さらにその中にICMPがありと階層構造を成しているので内側のICMP階層のみを見てみると以下のようです。

>>> ans[0].answer[ICMP]
<ICMP type=echo-reply code=0 chksum=0xffff id=0x0 seq=0x0 |<Padding load='ssNR\x00\x00\x00\x00\x00\x00\x00\x00 h\\x9d\x06n(' |>>

typeをみると echo-replay となっているので、ICMP成功していることが分かります。数値のまま読み取ると以下のようです。

>>> ans[0].answer[ICMP].type
0

パケットからのフィールド取り出し、パケットの組み立て、なんとかできた?ううむ、ちゃきちゃき出来るレベルには全然ですな。うさちゃんに付き合ってもらっていろいろ練習して行きたいと思います。

うさちゃんと一緒(4) Rabbit 4000 対 Python Scapy へ戻る

うさちゃんと一緒(6) Scapyから送ったUDPパケットをオウム返し、Rabbit 4000 へ進む