dpktはパケットを作成したり解析したりするPythonモジュールである。pcapファイルを読み込むときなどに使う。
バイト列をやりとりする通信プログラムを書く際に、バイト列の扱い方の参考になると思ってソースコードを読んでいた。
class Packet(_MetaPacket("Temp", (object,), {})):
の中で定義されている def pack_hdr(self):
は Packet
オブジェクトのヘッダフィールドを定められたフォーマットに従ってバイト列にするメソッドである。
https://github.com/kbandla/dpkt/blob/9a5157f605f6f68661e58c8b44e4b6364c208507/dpkt/dpkt.py#L151
def pack_hdr(self): """Return packed header string.""" try: return self._pack_hdr( *[getattr(self, k) for k in self.__hdr_fields__] ) except struct.error: (省略)
このソースコードを見て2つの疑問を抱いた。
*[getattr(self, k) for k in self.__hdr_fields__]
の頭にあるアスタリスク*
は何???self._pack_hdr(引数)
を呼び出しているけど、どこにもdef _pack_hdr(self, 引数)
のような定義は見当たらないぞ???
1. *[getattr(self, k) for k in self.__hdr_fields__]
の頭にあるアスタリスク *
は何???
まず1点目のアスタリスク *
は「引数リストのアンパック」を意味する。
*
の後ろにリストがあるが、 メソッド _pack_hdr
の引数にリストオブジェクトを1つ渡すのではなく、リストを展開して _pack_hdr
の引数として渡す。
Python公式チュートリアル に軽く説明が載っていた。
IPを例に上記のコードの動作を考えてみる。
class IP(dpkt.Packet)
で ヘッダフィールド __hdr__
は次のように定義されている。
__hdr__ = ( ('_v_hl', 'B', (4 << 4) | (20 >> 2)), ('tos', 'B', 0), ('len', 'H', 20), ('id', 'H', 0), ('off', 'H', 0), ('ttl', 'B', 64), ('p', 'B', 0), ('sum', 'H', 0), ('src', '4s', b'\x00' * 4), ('dst', '4s', b'\x00' * 4) )
https://github.com/kbandla/dpkt/blob/9a5157f605f6f68661e58c8b44e4b6364c208507/dpkt/ip.py#L21
クラス IP
の親は Packet
で、さらにその親は _MetaPacket
である。 クラス _MetaPacket
のメソッド __new__
は次のようになっている。
class _MetaPacket(type): def __new__(cls, clsname, clsbases, clsdict): t = type.__new__(cls, clsname, clsbases, clsdict) st = getattr(t, '__hdr__', None) if st is not None: # XXX - __slots__ only created in __new__() clsdict['__slots__'] = [x[0] for x in st] + ['data'] t = type.__new__(cls, clsname, clsbases, clsdict) t.__hdr_fields__ = [x[0] for x in st] t.__hdr_fmt__ = getattr(t, '__byte_order__', '>') + ''.join([x[1] for x in st]) t.__hdr_len__ = struct.calcsize(t.__hdr_fmt__) t.__hdr_defaults__ = dict(compat_izip( t.__hdr_fields__, [x[2] for x in st])) return t
https://github.com/kbandla/dpkt/blob/9a5157f605f6f68661e58c8b44e4b6364c208507/dpkt/dpkt.py#L32
変数 st
に先程の __hdr__
が代入され、 クラスオブジェクト IP
のインスタンス変数 __hdr_fields__
は次のようになる。
__hdr_fields__ = ( '_v_hl', 'tos', 'len', 'id', 'off', 'ttl', 'p', 'sum', 'src', 'dst', )
これらはクラスオブジェクト IP
のインスタンス変数名となる。これらの変数に値を代入する処理はクラス Packet
のメソッド __init__
にある。
self.unpack(args[0])
https://github.com/kbandla/dpkt/blob/9a5157f605f6f68661e58c8b44e4b6364c208507/dpkt/dpkt.py#L90
args[0]
はバッファである。メソッド unpack
は次の通り。バッファ、つまりパケットを作り上げるバイト列を struct.unpack
で解釈して、出てきた値を __hdr_fields__
で定義された各インスタンス変数に代入している。
def unpack(self, buf): """Unpack packet header fields from buf, and set self.data.""" for k, v in compat_izip(self.__hdr_fields__, struct.unpack(self.__hdr_fmt__, buf[:self.__hdr_len__])): setattr(self, k, v) self.data = buf[self.__hdr_len__:]
https://github.com/kbandla/dpkt/blob/9a5157f605f6f68661e58c8b44e4b6364c208507/dpkt/dpkt.py#L174
つまり、 ip = IP(buffer)
とすると、 ip.src
で送信元IPアドレスを取得できるというわけである。
ここまで読むと、本題の [getattr(self, k) for k in self.__hdr_fields__]
が何をしているのか理解できるようになる。
__hdr_fields__
で定義されたIPパケット ヘッダフィールド名のリストを順番に参照し、各フィールドの値をリストにして返しているのである。
そのリストをアンパックし、メソッド _pack_hdr
の引数に渡している、というのが1点目の疑問の答えだ。
2. self._pack_hdr(引数)
を呼び出しているけど、どこにも def _pack_hdr(self, 引数)
のような定義は見当たらないぞ???
続いて2点目の、メソッド _pack_hdr
がどこで定義されているか?という疑問の答えはクラス Packet
のメソッド __init__
の中にある。
self._pack_hdr = partial(struct.pack, self.__hdr_fmt__)
https://github.com/kbandla/dpkt/blob/9a5157f605f6f68661e58c8b44e4b6364c208507/dpkt/dpkt.py#L104
公式ドキュメントによると、 functools.partial(func, *args, **keywords)
は
新しい partial オブジェクト を返します。このオブジェクトは呼び出されると位置引数 args とキーワード引数 keywords 付きで呼び出された func のように振る舞います。
とのこと。
つまり、_pack_hdr(foo, bar, baz)
を呼び出すと、 struct.pack(self.__hdr_fmt__, foo, bar, baz)
が実行されることになる。
self.__hdr_fmt__
は フィールド foo, bar, baz
のそれぞれのフォーマット文字列を連結したもので、上述したIPの場合は __hdr_fmt__ = '>BBHHHBBH4s4s'
となる。
以上で2点目の疑問も解決した。
めでたしめでたし。