フロー識別テスト

コントローラプログラムの中で、各パケットを分析してプロトコルタイプや L4 ポート番号などフロー識別のための方法を調べる


コード記述準備

パケットの各フィールドの名前や値をどうやって得るか?

まず入力は ylbswitch.py であれば、
1. class ylbswitch の
2. def install の
3. inst.register_for_packet_in( ) で、
4. packet_in_callback( ) を指定し、
5. これが packet_in_callback(dpid, inport, reason, len, bufid, packet) を引数として呼ばれてくる

パース処理は packet.parse とするとパースしてくれる模様。(謎)
ところで中では誰かがどこかで packet.parse とやってパースしてやらないといけないはずだが、それは ylbswitch.py にはない。
パース済みかどうかは packet.parsed を参照すると得られるらしい。

パース済みであれば packet.src で Ethernet source address, packet.dst で destination address などが得られる模様。
が、このメンバ変数(プロパティ)の一覧がどこにあるか、そのドキュメントのありかが分からない。

しょうがないので実装を見る。src/nox/lib/packet/ 以下にいろいろ用意されている。 最初は packet.parse 処理をどこかで誰かがやってくれているようだが、その ethernet 以降の上層のパースについては iph = packet.find('ipv4') などして手でパースさせる必要がある。
例えばこのディレクトリにある ethernet.py には def parse として実装記述有り。 またここで parsed プロパティに True を設定している。

仕組みはともかく、名前の一覧が無いと困るのでソースを見る。
たとえば ethernet.py の parse( ) 実装を見ると、以下のプロパティが設定されることがわかる。

ethernet.py
	src, dst 	MAC アドレス
	type		プロトコル type
	hdr_len 	ヘッダ長
	payload_len	ボディ長

同様に ipv4.py を見ると __init__ では以下のようになってる。まあこれだろう。
        self.v     = 4
        self.hl    = ipv4.MIN_LEN / 4
        self.tos   = 0
        self.iplen = ipv4.MIN_LEN
        self.id    = int(time.time())
        ipv4.ip_id += 1
        self.flags = 0
        self.frag  = 0
        self.ttl   = 64
        self.protocol = 0
        self.csum  = 0
        self.srcip = 0
        self.dstip = 0

tcp.py は
        self.srcport  = 0 # 16 bit
        self.dstport  = 0 # 16 bit
        self.seq      = 0 # 32 bit
        self.ack      = 0 # 32 bit
        self.off      = 0 # 4 bits
        self.res      = 0 # 4 bits
        self.flags    = 0 # reserved, 2 bits flags 6 bits
        self.win      = 0 # 16 bits
        self.csum     = 0 # 16 bits
        self.urg      = 0 # 16 bits
        self.tcplen   = 20 # Options? 
        self.options  = [] 
	あと paser() でこんなのを出してる。
        self.hdr_len = self.off * 4
        self.payload_len = dlen - self.hdr_len
        self.tcplen = dlen

udp.py は
        self.srcport = 0
        self.dstport = 0
        self.len = 8
        self.csum = 0
        self.payload = ''
	あと paser() でこんなのを出してる。
        self.hdr_len = udp.MIN_LEN
        self.payload_len = self.len - self.hdr_len

icmp.py だと __init__ で
        self.type = 0
        self.code = 0
        self.csum = 0

などなど。
そしてこれらの変数は if ( packet.type == ethernet.LLDP_TYPE ) などとして比較されるのだが、この値がどこに定義されているかも謎。
これもやはり実装を見る?( src/nox/lib/packet/ 以下)

パケットの各フィールドの値に相当する定値の名前や値をどうやって得るか?

実装としては ethernet というオブジェクトの定義は(当然) ethernet クラスにある。
例えば pyswitch.py などにしても、冒頭で
from nox.lib.packet.ethernet     import ethernet
とやって import し、そこに含まれている class ethernet という定義をひっぱってきて、
    if packet.type == ethernet.LLDP_TYPE:
        return CONTINUE
などと書いている。(つまりこの ethernet というのはクラス名なのか!)

ethernet.py だと
定数として
ETHER_ANY            = "\x00\x00\x00\x00\x00\x00"
ETHER_BROADCAST      = "\xff\xff\xff\xff\xff\xff"
BRIDGE_GROUP_ADDRESS = "\x01\x80\xC2\x00\x00\x00"
LLDP_MULTICAST       = "\x01\x80\xc2\x00\x00\x0e"
PAE_MULTICAST        = '\x01\x80\xc2\x00\x00\x03' # 802.1x Port Access Entity
NDP_MULTICAST        = '\x01\x23\x20\x00\x00\x01' # Nicira discovery
__init__ の中に
    IP_TYPE   = 0x0800
    ARP_TYPE  = 0x0806
    RARP_TYPE = 0x8035
    VLAN_TYPE = 0x8100
    LLDP_TYPE = 0x88cc
    PAE_TYPE  = 0x888e           # 802.1x Port Access Entity

ipv4.py だと __init__ に
    IPv4 = 4
    ICMP_PROTOCOL = 1
    TCP_PROTOCOL  = 6
    UDP_PROTOCOL  = 17

udp.py には特に何も無し
tcp.py だと TCP option の名前とか、FIN, SYN, RST といったフラグビットとか。

icmp.py だと定数として以下のものあり。
TYPE_ECHO_REPLY   = 0
TYPE_DEST_UNREACH = 3
TYPE_SRC_QUENCH   = 4
TYPE_REDIRECT     = 5
TYPE_ECHO_REQUEST = 8
TYPE_TIME_EXCEED  = 11
CODE_UNREACH_NET     = 0
CODE_UNREACH_HOST    = 1
CODE_UNREACH_PROTO   = 2
CODE_UNREACH_PORT    = 3
CODE_UNREACH_FRAG    = 4
CODE_UNREACH_SRC_RTE = 5

実験

具体的にコントローラプログラムに渡されてくるパケットデータ(Ethernet フレーム)から、より上層の(プロトコルタイプに応じた)当該フィールドを取り出す方法について調べる。

Ethernet パケットから IP ヘッダを取り出す方法

実は example の dnsspy.py が結構サンプルとしては良い。 まず callback 関数として登録したパケット処理関数には packet という引数で「captured data」なるフォーマットでデータが渡ってくる、とある。 しかし実際にはここは packet と名付けられた引数で、かつ中身は ethernet インスタンスで渡される。 そこで Ethernet ヘッダのプロパティは packet.type や packet.src などとすれば直接取り出せる。

次に IP ヘッダを取り出すには、この ethernet インスタンスから IP ヘッダを取り出す必要がある。 なぜか find という関数名になっている。
iph = packet.find('ipv4')
などとする。へええ、という感じ。

問題はこの先、ip header ではなく ip body からどうやって TCP ヘッダを取り出すか?と思ったら find は ethernet packet そのものを相手にヘッダを取り出してくれる。(だから find ?)
まず IP ヘッダを取り出し、それのプロトコルを見て UDP だったら find で再び(Ethernet フレーム全体から) UDP ヘッダを取り出す、といった感じ。

            if packet.type == ethernet.IP_TYPE:
                iph = packet.find('ipv4')
                if iph.protocol == ipv4.ICMP_PROTOCOL:
                    # 何らか ICMP だった場合の処理           
                elif iph.protocol == ipv4.UDP_PROTOCOL:
                    udph = packet.find('udp')
                    if udph.dstport == 53:

この find で失敗すると(例えばICMPパケットの ethernet インスタンスに向かって find('udp') と言ったとか)、結果は no になる。

エラー判定のサンプルを dnsspy.py から取り出すと以下のようになる。
        dnsh = packet.find('dns')
        if not dnsh:
            log.err('received invalid DNS packet',system='dnsspy')
            return CONTINUE
こんな感じ。

プロトコルタイプを識別する例

というわけで「Ethernet で、IPv4で、ICMP かどうかを識別する」コードを検討する。
条件としては、
・packet.type が ethernet.IP_TYPE で
・packet.protocol が ipv4.ICMP_PROTOCOL であることを確認
できれば良い。

具体的には packet が ethrenet インスタンスなので、IP より上の層で判定する場合には手動で parse する必要が有る点に注意。

以下に pyswitch に手を入れる箇所を抜粋して説明する。

from nox.lib.packet.ethernet     import ethernet <<< これは元からあった
from nox.lib.packet.ipv4         import ipv4 <<< この行を追加した

def forward_l2_packet(dpid, inport, packet, buf, bufid):
    dstaddr = packet.dst.tostring()
    if not ord(dstaddr[0]) & 1 and inst.st[dpid].has_key(dstaddr):
        prt = inst.st[dpid][dstaddr]
        if  prt[0] == inport:
            log.err('**warning** learned port = inport', system="pyswitch")
            inst.send_openflow(dpid, bufid, buf, openflow.OFPP_FLOOD, inport)
        else:
            # Yasu update. add process depends packet type.  <<< ここから記述追加
            if packet.type == ethernet.IP_TYPE:
                iph = packet.find('ipv4')   <<< ここで ethernet インスタンスである packet から ipv4 インスタンスである iph を生成
                if iph.protocol == ipv4.ICMP_PROTOCOL:   << で、その後は iph インスタンスのプロパティを調べる
                    # 何らか ICMP だった場合の処理
                else:
                    # 何らか ICMP 以外の IP だった場合の処理
            else:
                # IP 以外(ARP 等)の処理   <<< ここまで

しばらくこれを頼りに書くが、何かしらリファレンスマニュアルがあるのだとは思う。



Yutaka Yasuda (yasuda [ at ] ylb.jp)