google/gopacket + libpcap で作るツールのパケットバッファ戦略

tcpdpの中のお話です

パケットを一時的に保持するバッファ

google/gopacket/pcap パッケージでパケットを取得するようなツールを作る場合、2つのバッファを持つことになります。

  1. libpcapが持つバッファ
  2. google/gopacketが持つバッファ(バッファ付きチャネル)。1000で固定。

パケットは加工されつつ上記のバッファを経由して、最後にgopacketからパケットを受けとることになります。

そしてバッファには、以下のような特徴があります。

  • libpcapが持つバッファが溢れたらパケットロスが発生します
  • gopacketのバッファサイズは固定です
  • libpcapは ImmediateMode がOFFだと、libpcapが持つバッファにある程度パケットを貯めてからまとめて返します

上記特徴からみても、ツールが正しく動くようにするためには いかにlibpcapのバッファを溢れさせないか が重要になってきます。

パケットロスを回避する戦略

そして、取れる戦略(とその戦略をとった場合のデメリット)は2つです。

  1. libpcapが持つバッファを大きくする ( bufferSize の調整 )
    • デメリット: bufferSize の分、最初に確保されるメモリ量が大きくなる
  2. libpcapからできるだけ早くパケットを回収する ( 例えば、immediateMode の有効化
    • デメリット: immediateMode が無効状態よりもCPUリソースを消費する

例えば tcpdp の場合、gopacketからパケット取得した後の、「パケットを加工して構造化ログに吐き出す機構」がボトルネックになります。まあ、そうですよね。

つまり、libpcap -> gopacket -> tcpdp の最後の処理に一番時間がかかります。パイプラインの最後の出口が詰まってしまう感じです。

これはもうどうしようもなさそうではあるのですが、もし

  • パケットの流量に波がある
  • パケットのピーク流量が一定時間発生しかなくて、それさえ耐えれば、以降の通常のパケット処理は十分処理可能である

と予想されるのであれば、「ツール内にlibpcap、gopacketに続く3つ目のバッファを持って、そのバッファに処理待ちパケットを溜め込む」という選択肢が取れます。 つまり戦略2です。

f:id:k1LoW:20190813115633p:plain

tcpdpだと internalBufferLength というパラメータで指定するのですが、これは単なるバッファ付きチャネルです。

図で言うとlibpcapからガンガンパケットを回収して、早々にtcpdpのバッファに溜めてしまいます。

そして、そのバッファに溜まったパケットを逐次処理していきます。tcpdpのバッファが溢れるまではパケットロスが生まれないという仕組みです。

まとめ

google/gopacket + libpcapで作るパケットのバッファリングの仕組みと、パケットロスを発生させないようにするバッファ戦略について紹介しました。

正直、結構戦略としては苦しいものなのですが、tcpdpだとパケットはシーケンシャルに処理しないといけなかったりするのでいまのところこのような実装になっています。

何かもっと良い方法があれば教えてください。