ぐるんぐるん

おもむくままに書いてます。

Pythonでパケット解析(dpkt) その2~上位レイヤーの内容の取得~

1の続き

Pythonでパケット解析(dpkt) その1~簡単な読み込み~ - ぐるんぐるんの続き。
今回は、http通信の通信を取るところまで頑張りたいです。

実際にやってみた

上位と下位の関係性

その1でも言ったように、あるレイヤーの小包は、

  • 小包=宛名(ヘッダー)+中身(ペイロード
  • 中身=上のレイヤーの小包

になっています。

なおHTTPは、Ethernet->IP->TCP->HTTPとなっています。これを頭に入れて、前回のhttp.capでいろいろ遊んでみたいと思います。

上位レイヤーの判別を行う

前回読み込んだethですが、こいつの中身(eth.data)がIPなのかどうかというのは、typeで判別することが出来ます。なおクラスと判別するので、dpkt.(小文字のプロトコル名).(大文字のプロトコル名)と判別します。

eth = dpkt.ethernet.Ethernet(buf)
if type(eth.data) == dpkt.ip.IP:
 print "IPパケットだよ!!"

パケットは入れ子になっているので、どんどん判別していく。

eth = dpkt.ethernet.Ethernet(buf)
if type(eth.data) == dpkt.ip.IP:
 ip = eth.data
 print "IPパケットだよ!!"
 if type(ip.data) == dpkt.tcp.TCP:
  print "TCPパケットだよ"

なお、HTTPはこれで判別できないようです。

ヘッダの情報を取り出す

小包の中身はとりあえずおいておき、宛名の情報をいろいろ抜き出してみましょう。これには、郵便番号やら「割れ物注意」的な感じでいろんな情報があります。たとえば、宛先のIPやらポートやら、いろいろなフラグがあります。

これらは、普通に参照することで取得することが出来ます。わー便利ー。IPパケットは送信元、受信先のIPアドレスを参照することが出来ます。

eth = dpkt.ethernet.Ethernet(buf)
if type(eth.data) == dpkt.ip.IP:
 ip = eth.data
 print "IPパケットだよ!!"
 print "IP: %s -> %s" % (socket.inet_ntoa(ip.src), socket.inet_ntoa(ip.dst))

TCPでは、IP同様に宛先のポートを取得することが出来ます。

eth = dpkt.ethernet.Ethernet(buf)
if type(eth.data) == dpkt.ip.IP:
 ip = eth.data
 print "IPパケットだよ!!"
 print "IP: %s -> %s" % (socket.inet_ntoa(ip.src), socket.inet_ntoa(ip.dst))
 if type(ip.data) == dpkt.tcp.TCP:
  print "TCPパケットだよ"
  tcp = ip.data
  print "","", "Port: %d -> %d" % (tcp.sport, tcp.dport)

HTTPが取れない

HTTPには、リクエストとレスポンスが存在する。リクエストは通常、クライアント→サーバーの通信で、レスポンスはその逆である。

HTTPに関する通信は原則、80番ポートを使う。そのため、TCPパケットで送信または受信ポートが80ポートかどうかで、HTTPかどうか、またリクエストかレスポンスかを判別した。また、headerはheadersで取得できる…ということである。

#HTTP Requestかの判定
if tcp.dport == 80 and len(tcp.data) > 0:
 httpreq = dpkt.http.Request(tcp.data)
 print "Rquest:", httpreq.headers

#HTTP Responseかの判定
if tcp.sport == 80 and len(tcp.data) > 0:
 httpres = dpkt.http.Response(tcp.data)
 print "Response:", httpres.headers

が、見事にエラーをはいてくれた。エラー内容的には、「HTTPパケットは、大きすぎるとTCPレイヤーで分割されて送るんだけれど、それは厳密にはHTTPではないんだよ」的なエラー*1。これをdpktでそのままでは読めない。

暗中模索でやってみた

色々あさってみると、以下の傾向があった。

  • IPレイヤーでDon't Fragmentフラグが立ってる
  • TCPレイヤーでPUSHフラグが立ってない
#HTTP Requestかの判定
if tcp.dport == 80 and len(tcp.data) > 0:
 httpreq = dpkt.http.Request(tcp.data)
 print "Rquest:", httpreq.headers

#HTTP Responseかの判定
if tcp.sport == 80 and len(tcp.data) > 0 and (dpkt.ip.IP_DF & ip.off) == 0:
 httpres = dpkt.http.Response(tcp.data)
 print "Response:", httpres.headers

しめたーと思ってやってみましたが、無理でした。どうしたらいいんでしょう、これ。

コード(全体)

# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from __future__ import with_statement

import dpkt
import socket

with open("http.cap", "rb") as f:
    pcr = dpkt.pcap.Reader(f)
    count = 0
    for ts, buf in pcr:
        count += 1
        print "Packet %d:%d" % (count, ts)
        try:
            eth = dpkt.ethernet.Ethernet(buf)
        except:
            print "Error: Packet No.%d" % count
        #IPパケットかの判定
        if type(eth.data) == dpkt.ip.IP:
            ip = eth.data
            print "", "IP: %s -> %s" % (socket.inet_ntoa(ip.src), socket.inet_ntoa(ip.dst))

            #TCPパケットかの判定
            if type(ip.data) == dpkt.tcp.TCP:
                tcp = ip.data
                print "","", "Port: %d -> %d" % (tcp.sport, tcp.dport)

                #HTTP Requestかの判定
                if tcp.dport == 80 and len(tcp.data) > 0:
                    httpreq = dpkt.http.Request(tcp.data)
                    print "", "", "Rquest:", httpreq.headers


                #HTTP Responseかの判定
                if tcp.sport == 80 and len(tcp.data) > 0 and (dpkt.ip.IP_DF & ip.off) == 0:
                    httpres = dpkt.http.Response(tcp.data)
                    print "", "", "Response:", httpres.headers

*1:厳密には、「TCP segment of a reassembled PDU」