Dockerの仮想NICでraw socketプログラミングをやってみる

前回に引き続き、Dockerのネットワーク周りです。
今回はDockerの仮想NICをraw socketで扱ってみようと思います。

Dockerの仮想NICはホストOS上で見えるvethとip linkの関係にあり、対応するvethにパケットを送信するとコンテナ内のethに転送されます。
そしてホストOS上で見えるvethは、通常のNICと同じように扱えます。
Docker標準ではこのvethがLinux bridgeであるdocker0というブリッジに接続されますが、前回はvethをdocker0から剥がしてOpenvSwitchに接続しました。
これによりVLANが扱えたり、OpenFlowでパケット転送を制御したりが可能となりました。
今回は、そのOpenvSwitchに当たる転送処理を自前で実装してみようというのが趣旨になります。
まずは簡単なバカハブ(リピーターハブ)を実装してみたいと思います。

まずは適当にコンテナを2つほど立てます。

次に各コンテナのeth0に相当するvethを探します。
毎回手で探すのは面倒なので、今後のためにこんなコードを書きました。
前回、コンテナ内のNICのiflinkと、ホストOS側vethのifindexが同値であることを利用して探す)

ビルドして実行してみる

ifindexからinterfaceの名前を取ってこれます。
これをうまいこと利用して、こんな感じで。

各コンテナ内のiflinkの値を引数として渡してあげると、ホストOS側のvethが一発で取得可能。
便利便利。

で、例によってデフォルトでvethはdocker0に接続されているので、接続を解除。

あと、コンテナ内のNICのアドレスも確認しておきましょう。
必要があればアドレスの変更も。
(今回はハブを作るので、2つのコンテナは同じサブネット上にいる必要があります。)

host1[eth0] : 172.17.0.2/16
host2[eth0] : 172.17.0.3/16

これで準備は完了です。

ここからは普通のraw socketプログラミングです。
raw socketはイーサフレームを直接送受信できるとても便利な機能です。
ちょうど今勉強中であまり詳しくないので、コードが汚いのは許してください。。
一応Ubuntu16.04 + Intel Celeron J1900で動かしています。
バイトオーダ等の違いで動かないとかあるかもしれませんが、そのあたりはご愛嬌。
(ハードウェアを意識したプログラミングって難しい。。)

ちなみに、このあたりを参考にしました(結構コピペ)
How to send/receive raw packets on Linux
イーサネットフレーム転送プログラム(RAWソケットプログラム) – ちーちーの小ネタ部屋

ビルドはこんな感じで。

これがリピーターハブとして動作するコードになります。
あるポートから受信したイーサフレームを、受信ポート以外のポートへフラッディングするだけです。
引数でNICを渡すと、それらのNICの全通信を他のポートに送信します。

では試してみましょう。

まずは作成したハブ動かしてない状態。
vethがどこにも接続されていないため、当然通信はできませんし、ARPすら解決できてない。

そして次にハブを起動してみる。
引数としてブリッジしたいインターフェース(今回はホストOS側で認識しているveth)を指定します。

再びPingを打つと正常に通信できていることが確認できました。

今回はデバッグのために通過したパケットの種別、受信インターフェイス、送信インターフェイスを標準出力するようにしています。

この出力から分かるように、コンテナ内のeth0に送出されたICMPパケットが、ホストOS側のvethにやってきています。
そのパケットをraw socketで吸い上げて、他のインターフェイス(veth)に送出し、それが再びコンテナ内のeth0に転送されています。

物理NICであっても、vethであっても全く同じコードでraw socketが動作するのが非常に便利です。
簡単に物理NICとDokcerのコンテナをブリッジできたりします。
今回はハブを作りましたが、少し頭を良くしてスイッチにしたり、VLANを扱えるようにしたり、ルータを作ったり、色々楽しそうですね。
フレーム単位でパケットを処理できて、TCP/UDPのペイロード等もその気になれば見れるので、DPIみたいな高度なものを実装するのもいいと思います。
(ただしraw socketだとあまりパフォーマンスは出ないと思うので、
 本気でやるならカーネルモジュールとして実装したり、netmapやDPDK等のフレームワークの使用を検討した方がいいでしょう。)

これでraw socketプログラミングやるためだけに、多ポートサーバを用意しなくていいので良いですね。
(買ったあとにこの方法思いついて微妙に無駄な出費になった。)
特にケーブルの繋ぎ変えもいらないのが気に入ってます。
ホストを増やしたくなったらコンテナ立てるだけでいいですし、いらなくなったら消すだけ。
サーバやトラフィック/パケットジェネレータもコンテナで立ててしまえば、試験が簡単にできます。

これでリモート環境のみでraw socketプログラミングができるようになったので、
家にいなくても(物理ケーブル繋ぎ変えもいらないので)遊べますね!

コメントを残す

Your email address will not be published / Required fields are marked *