[Network][Linux] 実機でLagopus Switchを作る

ワタシのSDNに対する理解

これまで、Openflowコントローラであるryu、openflowシミュレータみたいなものであるmininetを組み合わせてちょろりちょろりと遊んできました。ただ、どうしてもOpenflowコントローラに対するアプリとネットワークとの組み合わせについて、理解が及ばない点が結構あり、もう少し直感的に触れるかも知れないOpenDayLightに手を出してみました。

ところが、OpenDayLight、最新バージョンはNitrogen(0.7.0)なんですが、使ってみるとめちゃくちゃ重たい。その上少々仕様(featureの構成が変わったり)が変わっていてまずそこから何が何だか分からない、トドメにクライアントのブラウザ負荷も半端ない・・・・Orz

そんなわけで、コントローラに対する理解をすすめるのは一旦おやすみし、じゃぁどこを理解しようか?ということで、データプレーン部分に手を出しました。

Lagopus Switchとは?

ホームページはここみたいです。OpenFlowスイッチ部分(データプレーン)を担うソフトウェアです。Linuxをインストールしたサーバに組み込むことで、サーバのNICをスイッチポートとすることができます。

OpenFlow1.3準拠となっており、10月開催されたOSCで一番興味を惹かれたソフトウェアです。本記事ではこれのセットアップ~一通りあそんでヽ(=´▽`=)ノとなったところまでを載せていこうと思います。

ハード選定

今回、VMだなんて寂しいことはしません。漢(をとこ)ならどかーんと物理マシンを用意します。とは言え、控えのHA-8000/RS220を使うのはもったいないし、電気代かかるし、省電力マシンがないかなーということで探したらありました。捨てなくてよかった。

富士通PRIMERGY TX120 S2です。CPUはCore2Duo P8700、6GB RAM、500GB SATA x2(RAID1)と言う代物です。もう骨董品の香りすらしますが、これがまだ動いてくれるんだよなぁと。こいつに、HA-8000に取り付けては持て余していた増設NICを全部移し替え、NICポートを合計7ポート構成にしときます。

ちなみにこの時、フロントベゼルの鍵をなくしたままロックしており、コレを破壊するのに苦労しました・・・Orz一旦ケース外して、ラジオペンチでヒネヒネしたり・・・・あー、疲れた。

想定構成

こんな感じにします。オンボードのポートは管理用としてスイッチポートにはせず、そのままOS間の通信用に使用します。増設NICはすべてスイッチポートとして使います。このスイッチを制御するOpenFlowコントローラはRyuを使用します。コントローラ⇔データプレーン間の通信はサーバセグメントを経由します。

最終目的としては、サンプルアプリに前回使ったSTP機能付きL2が動かせたら良いなーと思っています。

OSですが、本家gitサイトのQuick Startにならい、Ubuntuを導入しました。それでは、以下インストール・セットアップに向けた記載を続けていきます。

OSのアップグレード

# apt-get update

取り敢えずアップデートしようと思い、updateコマンドを実行した所、なんとコア吐いて落ちましたOrz

*** Error in `appstreamcli': double free or corruption (fasttop): 0x000000000149b0f0 ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x77725)[0x7fc0c1148725]
/lib/x86_64-linux-gnu/libc.so.6(+0x7ff4a)[0x7fc0c1150f4a]
     :
     :

えーじゃぁ、upgradeするよーとしてみたら、こいつは通りました。

# apt-get upgrade

うまく行ったので再起動

# shutdown -r now

その後再度update

# apt-get update

今度はうまく行ったのでまた再起動

# shutdown -r now

前提パッケージのインストール

# apt-get install build-essential libexpat-dev libgmp-dev libssl-dev libpcap-dev byacc flex git python-dev python-pastedeploy python-paste python-twisted

Lagopusのダウンロード

gitサイトからcloneして拾います。

# git clone https://github.com/lagopus/lagopus

masterのまんま突入するんで、checkoutしませんぬ。この状態だと0.2.12-releaseが導入されることになります。

DPDK使用のためのカーネルヘッダ取得

Intel DPDK(Data Plane Development Kit)を使うためのカーネルヘッダ取得を行います。DPDKとは、ネットワーク処理に特化したアプリケーションを作成するために使われるソフトウェア・ライブラリだそうで、これを使用することにより、データプレーンの速度向上が図れるようです。

まずそれを導入するためのカーネルヘッダを取得してみます。

# apt-get install linux-headers-$(uname -r)

ダウンロードするものはどうやらなかったようです。そのまま次へ進めます。

linux-headers-4.4.0-21-generic はすでに最新バージョン (4.4.0-21.37) です。
linux-headers-4.4.0-21-generic は手動でインストールしたと設定されました。
以下のパッケージが自動でインストールされましたが、もう必要とされていません:
 libpango1.0-0 libpangox-1.0-0 ubuntu-core-launcher
これを削除するには 'apt autoremove' を利用してください。
アップグレード: 0 個、新規インストール: 0 個、削除: 0 個、保留: 27 個。

configureの実行

# cd lagopus
# ./configure

Lagopusはどうやらソースをコンパイルしてインストールする形態のパッケージみたいですね。./configureとか何年ぶりに叩いたやろか・・・・

なお、この処理の中で併せてIntel DPDKがインストール処理まで完了しちゃいます。なので、結構時間がかかります。

パッケージのビルド

# make

パッケージのインストール

# make install

DPDKカーネルモジュールの読み込み

ここからは、Intel DPDKで使用するドライバを実際にスイッチ対象とするポートに当てていきます。まず./configre時にビルドされたカーネルモジュールを取り込みます。

# modprobe uio
# insmod ./src/dpdk/build/kmod/igb_uio.ko
# insmod ./src/dpdk/build/kmod/rte_kni.ko

hugepageをDPDKが使用できるようにします。Quick Startでは2つの手法が記載されていますが、私は1.の手法を採用しました。

# sh -c "echo 256 > /sys/devices/system/node/node0/hugepa ges/hugepages-2048kB/nr_hugepages"
# mkdir -p /mnt/huge
# mount -t hugetlbfs nodev /mnt/huge

■確認
# mount

■以下のが出てればOK
nodev on /mnt/huge type hugetlbfs (rw,relatime)

ところで

PRIMERGY TX120 S2の今回の構成はこんな感じになっています。

これは、まだDPDKを適用する前の状況なんですが、このような感じでNICは認識されています。この中の拡張NICに対してDPDKを適用し、Lagopus Switchのポートとして認識出来るようにしていきます。

ドライバの適用状況確認

# ./src/dpdk/usertools/dpdk-devbind.py --status

出力結果がこんなふうに出ます。

Network devices using DPDK-compatible driver
============================================
<none>

Network devices using kernel driver
===================================
0000:00:19.0 '82567LM-4 Gigabit Network Connection 10e5' if=enp0s25 drv=e1000e unused=igb_uio *Active*
0000:20:00.0 '82571EB Gigabit Ethernet Controller 105e' if=enp32s0f0 drv=e1000e unused=igb_uio 
0000:20:00.1 '82571EB Gigabit Ethernet Controller 105e' if=enp32s0f1 drv=e1000e unused=igb_uio 
0000:30:00.0 '82580 Gigabit Network Connection 150e' if=enp48s0f0 drv=igb unused=igb_uio 
0000:30:00.1 '82580 Gigabit Network Connection 150e' if=enp48s0f1 drv=igb unused=igb_uio 
0000:30:00.2 '82580 Gigabit Network Connection 150e' if=enp48s0f2 drv=igb unused=igb_uio 
0000:30:00.3 '82580 Gigabit Network Connection 150e' if=enp48s0f3 drv=igb unused=igb_uio

全部デフォルトのカーネルドライバを使っていることがわかります。

DPDKドライバへの適用

# ./src/dpdk/usertools/dpdk-devbind.py --bind=igb_uio 0000:20:00.0 0000:20:00.01 0000:30:00.0 0000:30:00.01 0000:30:00.02 0000:30:00.03

結果の確認

# ./src/dpdk/usertools/dpdk-devbind.py --status

こんなふうに変わります。

Network devices using DPDK-compatible driver
============================================
0000:20:00.0 '82571EB Gigabit Ethernet Controller 105e' drv=igb_uio unused=
0000:20:00.1 '82571EB Gigabit Ethernet Controller 105e' drv=igb_uio unused=
0000:30:00.0 '82580 Gigabit Network Connection 150e' drv=igb_uio unused=
0000:30:00.1 '82580 Gigabit Network Connection 150e' drv=igb_uio unused=
0000:30:00.2 '82580 Gigabit Network Connection 150e' drv=igb_uio unused=
0000:30:00.3 '82580 Gigabit Network Connection 150e' drv=igb_uio unused=

Network devices using kernel driver
===================================
0000:00:19.0 '82567LM-4 Gigabit Network Connection 10e5' if=enp0s25 drv=e1000e unused=igb_uio *Active*

設定ディレクトリの作成

# mkdir /usr/local/etc/lagopus

設定サンプルファイルのコピーとバックアップ

# cp -p ./misc/examples/lagopus.dsl /usr/local/etc/lagopus/
# cp -p /usr/local/etc/lagopus/lagopus.dsl /usr/local/etc/lagopus/lagopus.dsl.orig

既存ファイルを参考に、設定を起こしていきます。取り敢えず、Lagopus側でやれることというのは

  • OpenFlowコントローラの指定
  • ハードウェア上のポートとインタフェースの紐付け
  • インタフェースとポート番号の紐付け
  • ポート番号とコントローラ設定をひとまとめにブリッジとして形成

みたいです。実際のコンフィグは以下のとおりです。

channel ryu-mgr create -dst-addr 192.168.100.138 -protocol tcp
controller controller01 create -channel ryu-mgr -role equal -connection-type main

interface interface01 create -type ethernet-dpdk-phy -port-number 0
interface interface02 create -type ethernet-dpdk-phy -port-number 1
interface interface03 create -type ethernet-dpdk-phy -port-number 2
interface interface04 create -type ethernet-dpdk-phy -port-number 3
interface interface05 create -type ethernet-dpdk-phy -port-number 4
interface interface06 create -type ethernet-dpdk-phy -port-number 5

port port01 create -interface interface01
port port02 create -interface interface02
port port03 create -interface interface03
port port04 create -interface interface04
port port05 create -interface interface05
port port06 create -interface interface06

bridge bridge01 create -controller controller01 -port port01 1 -port port02 2 -port port03 3 -port port04 4 -port port05 5 -port port06 6 -dpid 0x1
bridge bridge01 enable
  • channelとして、openflowコントローラの指定を行う
  • channel設定をもって、openflowコントローラのオブジェクトを作成する(controller01)
  • DPDKを適用したPHY順にinterfaceを作成する(interface0[123456])
  • interface定義に基づき、portの設定を行う(port0[123456])
  • ブリッジを作成する(bridge01)
    • コントローラはcontroller01を指定
    • ポートは定義した順に1から連番でport0[123456]を適用する
    • dpidは0x1に指定する
  • ブリッジを有効にする

というわけで、ここまでできたらLagopusの設定まで完了となります。ヽ(=´▽`=)ノ

今回のお遊び想定構成

今回はこんなことをしてみます。

Lagopusスイッチを我が家のバックエンドセグメントに接続し、加えてメインノートPCを接続します。バックエンドセグメントはクライアントセグメントを兼ねており、DNS/NTPサーバがDHCP配信をします。ちゃんとスイッチとして動いていれば、このアドレス受信が可能なはずです。

また、クライアントとしての通信も一通り出来るはずです。

OpenFlowコントローラ起動

OpenFlowコントローラ側で「simple_switch_13.py」を起動します。このサンプルアプリの「13」って、OpenFlow1.3って意味だったんですね。(今更知りました)

# ryu-manager simple_switch_stp_13.py

loading app simple_switch_stp_13.py
loading app ryu.controller.ofp_handler
instantiating app None of Stp
creating context stplib
instantiating app simple_switch_stp_13.py of SimpleSwitch13
instantiating app ryu.controller.ofp_handler of OFPHandler

コレで準備完了。

Lagopus Switchの起動

スイッチを起動します。「Simple run」の引数で起動しました。

# lagopus -d -- -c3 -n1 -- -p3
以下のように表示されれば大丈夫そうです。
EAL: Detected 2 lcore(s)
EAL: Probing VFIO support...
EAL: WARNING: cpu flags constant_tsc=yes nonstop_tsc=no -> using unreliable clock cycles !
EAL: PCI device 0000:20:00.0 on NUMA socket -1
EAL: probe driver: 8086:105e net_e1000_em
EAL: PCI device 0000:20:00.1 on NUMA socket -1
EAL: probe driver: 8086:105e net_e1000_em
EAL: PCI device 0000:30:00.0 on NUMA socket -1
EAL: probe driver: 8086:150e net_e1000_igb
EAL: PCI device 0000:30:00.1 on NUMA socket -1
EAL: probe driver: 8086:150e net_e1000_igb
EAL: PCI device 0000:30:00.2 on NUMA socket -1
EAL: probe driver: 8086:150e net_e1000_igb
EAL: PCI device 0000:30:00.3 on NUMA socket -1
EAL: probe driver: 8086:150e net_e1000_igb
Initialization completed.
NIC RX ports:

I/O lcore 1 (socket 0):
 RX ports:
 Output rings:

Worker 0: lcore 1 (socket 0):
 Input rings:
 Output rings per TX port

NIC TX ports:

Ring sizes:
 NIC RX = 1024
 Worker in = 1024
 Worker out = 1024
 NIC TX = 1024
Burst sizes:
 I/O RX (rd = 32, wr = 32)
 Worker (rd = 32, wr = 32)
 I/O TX (rd = 32, wr = 32)

Initializing NIC port 0 ...
Initializing NIC port 1 ...
Initializing NIC port 2 ...
Initializing NIC port 3 ...
Initializing NIC port 4 ...
Initializing NIC port 5 ...
Logical core 1 (io-worker 0) main loop.
PMD: eth_em_interrupt_action(): Port 1: Link Up - speed 1000 Mbps - full-duplex

Ryu側ではSTPのコンバージェンスが終わったようです。

[STP][INFO] dpid=0000000000000001: [port=2] DESIGNATED_PORT / DISABLE
[STP][INFO] dpid=0000000000000001: [port=2] Link up.
[STP][INFO] dpid=0000000000000001: [port=2] DESIGNATED_PORT / LISTEN
[STP][INFO] dpid=0000000000000001: [port=1] DESIGNATED_PORT / LEARN
[STP][INFO] dpid=0000000000000001: [port=3] DESIGNATED_PORT / LEARN
[STP][INFO] dpid=0000000000000001: [port=4] DESIGNATED_PORT / LEARN
[STP][INFO] dpid=0000000000000001: [port=5] DESIGNATED_PORT / LEARN
[STP][INFO] dpid=0000000000000001: [port=6] DESIGNATED_PORT / LEARN
[STP][INFO] dpid=0000000000000001: [port=2] DESIGNATED_PORT / LEARN
[STP][INFO] dpid=0000000000000001: [port=1] DESIGNATED_PORT / FORWARD
[STP][INFO] dpid=0000000000000001: [port=3] DESIGNATED_PORT / FORWARD
[STP][INFO] dpid=0000000000000001: [port=4] DESIGNATED_PORT / FORWARD
[STP][INFO] dpid=0000000000000001: [port=5] DESIGNATED_PORT / FORWARD
[STP][INFO] dpid=0000000000000001: [port=6] DESIGNATED_PORT / FORWARD

クライアントをLagopus Switchに接続してみます。

スイッチ側にはこんなログが出ました。

PMD: eth_igb_interrupt_action(): Port 4: Link Up - speed 1000 Mbps - full-duplex

Ryu側にはこんなログが出ました。

[STP][INFO] dpid=0000000000000001: [port=5] Link up.
[STP][INFO] dpid=0000000000000001: [port=5] DESIGNATED_PORT / LISTEN
[STP][INFO] dpid=0000000000000001: [port=2] DESIGNATED_PORT / FORWARD
[STP][INFO] dpid=0000000000000001: [port=2] Link down.
[STP][INFO] dpid=0000000000000001: [port=2] DESIGNATED_PORT / DISABLE
[STP][INFO] dpid=0000000000000001: [port=2] Link up.
[STP][INFO] dpid=0000000000000001: [port=2] DESIGNATED_PORT / LISTEN
[STP][INFO] dpid=0000000000000001: [port=5] DESIGNATED_PORT / LEARN
[STP][INFO] dpid=0000000000000001: [port=2] DESIGNATED_PORT / LEARN
[STP][INFO] dpid=0000000000000001: [port=5] DESIGNATED_PORT / FORWARD
packet in 1 8c:c1:21:50:49:2b 33:33:00:00:00:16 5
packet in 1 8c:c1:21:50:49:2b 01:80:c2:00:00:0e 5
packet in 1 8c:c1:21:50:49:2b 33:33:00:01:00:02 5
packet in 1 8c:c1:21:50:49:2b 33:33:00:00:00:01 5

若干スイッチ間接続で四苦八苦してたので、変なログが出てますが、最終的にはうまく繋がったようです。フレームも流れ始めてるように見えます。

この状態でWindows側のEthernetポートのIP設定を確認します。

イーサネット アダプター イーサネット 3:

接続固有の DNS サフィックス . . . . .: bluecore.net
 説明. . . . . . . . . . . . . . . . .: Intel(R) 82579LM Gigabit Network Connection
 物理アドレス. . . . . . . . . . . . .: 8C-C1-21-50-49-2B
 DHCP 有効 . . . . . . . . . . . . . .: はい
 自動構成有効. . . . . . . . . . . . .: はい
 IPv6 アドレス . . . . . . . . . . . .: 2001:470:fc89:220:****:****:****:25be(優先)
 一時 IPv6 アドレス. . . . . . . . . .: 2001:470:fc89:220:****:****:****:900f(優先)
 リンクローカル IPv6 アドレス. . . . .: fe80::11e4:****:****:25be%18(優先)
 IPv4 アドレス . . . . . . . . . . . .: 192.168.220.38(優先)
 サブネット マスク . . . . . . . . . .: 255.255.255.0
 リース取得. . . . . . . . . . . . . .: 2017年11月8日 23:35:14
 リースの有効期限. . . . . . . . . . .: 2017年11月9日 0:35:12
 デフォルト ゲートウェイ . . . . . . .: fe80::***:56ff:***:4c3c%18
 192.168.220.254
 DHCP サーバー . . . . . . . . . . . .: 192.168.220.202
 DHCPv6 IAID . . . . . . . . . . . . .: 327991585
 DHCPv6 クライアント DUID. . . . . . .: ****
 DNS サーバー. . . . . . . . . . . . .: 192.168.220.202
 192.168.239.202
 NetBIOS over TCP/IP . . . . . . . . .: 有効

その後、ネットサーフィンしてみたり、Mastodonしてみたりしましたが、快調でした。その間、ガンガンフレームの情報がRyu側のログに流れてました。デバッグモードで実行しているというのも一つの要因なんですかねー。

終了

気が済んだところでPCのケーブル抜線&Lagopus Switch, Ryuの停止をしました。Lagopus/Ryuの停止は、それぞれCtrl+Cするだけです。フォアグラウンドで動作しているので。

感じたこと

やっぱりPython勉強しないとダメだなぁと痛感しました。コントローラの処理の考え方がまずわからない。
サンプルコードにしても、L2スイッチ、しかも802.1dなスイッチを実用させても何だかなぁという気がしますし、L3スイッチの設定はそもそも何が出来るか分かってない。サンプルコードベースにカスタマイズする形でもいいから、何らかの手を用いて理解を深める必要があると感じました。
その他、現状だとRyuもLagopusもフォアグラウンドで動いてるので、コレを完全にバックグラウンド動作させないと運用には耐えられませんし、そのことも踏まえて稼働させることを考えないと実用には・・・・道のりはものすごく遠そうな気がします。

というわけで、アプリケーションコード読んで勉強します。はい。開発能力ゼロで本当にすみませんOrz

あとなぁ・・・・Ryu側とLagopus側で対応するポート番号にずれが生じているのが気になりますね。これもコントローラ側のソースが理解できたら、少しは補正することが出来るのかなぁなんて。

追記:2017/11/10

困ったことが起きた

翌日、Lagopus Switchで遊ぼうと色々やってたらあることに気づきまして。simple_switch_stp_13.pyをRyuで稼働させた所、リンクダウン/アップを繰り返していつまでたってもSTPがコンバージェンスせんのです。で、プロセスを確認してみると、CPU使用率が100%。こりゃどういうことだということで調べてみると、DPDKに原因があることがわかりました。

さて、どうしよう

DPDKがCPUをフル稼働させてパフォーマンスを叩き出すことはわかりました。ただ、リンクダウン/アップを繰り返す挙動はなんとかしないと、そもそもSDNの勉強ができまてん。というわけで、渋々一旦はDPDKを無効化することにしました。

configureのしなおし-ビルド&インストール

# cd lagopus
# make clean
# ./configure --disable-dpdk
# make
# make install

DPDKを無効化した場合、DPDKのビルドが発生しない分結構あっさりとconfigure/ビルドが片付きます。

再起動&IPv4/IPv6無効化

再起動を一旦行い、DPDKドライバが外れた状態で起動します。(DPDK適用する場合、一々手で適用しなきゃならんのですよ・・・)

スイッチ用NICは普通のNICとして起動し、IPv4/IPv6有効状態で起動してしまいますので、これらすべてを無効に設定して回ります。

設定変更

lagopus.dslを修正します。

実際のコンフィグは以下のとおりです。

channel ryu-mgr create -dst-addr 192.168.100.138 -protocol tcp
controller controller01 create -channel ryu-mgr -role equal -connection-type main

interface interface01 create -type ethernet-rawsock -device enp32s0f0 ★
interface interface02 create -type ethernet-rawsock -device enp32s0f1 ★
interface interface03 create -type ethernet-rawsock -device enp48s0f0 ★
interface interface04 create -type ethernet-rawsock -device enp48s0f1 ★
interface interface05 create -type ethernet-rawsock -device enp48s0f2 ★
interface interface06 create -type ethernet-rawsock -device enp48s0f3 ★
port port01 create -interface interface01
port port02 create -interface interface02
port port03 create -interface interface03
port port04 create -interface interface04
port port05 create -interface interface05
port port06 create -interface interface06

bridge bridge01 create -controller controller01 -port port01 1 -port port02 2 -port port03 3 -port port04 4 -port port05 5 -port port06 6 -dpid 0x1
bridge bridge01 enable

★箇所が修正しt箇所です。typeを「ethernet-rawsock」に変更し、物理インタフェースをNICデバイス名で指定します。これでDPDKではなく、通常のNICをLagopus Switchにスイッチ用ポートとして認識させられます。

スイッチの起動

スイッチを起動します。「Simple run」の引数で起動しました。

# lagopus -d --

「-c3 -n1 — -p3」はDPDKのオプションですので外します。ryu側では引き続きsimple_switch_stp_13.pyを稼働させましたが、ループ構成にしてもちゃんとコンバージェンスしましたし、通信も安定して行えました。CPU使用率は3%前後に落ち着き、Load AverageもDPDK有効時は1.8辺りを記録していたのが一気に落ち着きました。

原因は未だにわかりませんが、流石に掘り下げる気力が残ってませんでした。無念。

今後やろうと思ってること

実は現在かんたんなソースは読み始めていて、どちらかと言うと「あー、スイッチってこういうふうにMACアドレスを学習するのね」とか、「あー、BPDUはこうして回しているのね」など、色々学びがありました。その中で、どうやらREST APIと連携してルーターの設定とかをぶち込めるという仕様を知り、試しにrest_router.pyとrest_config_switch.pyを稼働させようかなと思ってます。

curl使ってIPアドレスの割当をするところまではDPDK実装時点で一度やったんですが、REST APIって色んな所で使われてるじゃないですか。せっかくなので具体的に学びたいなとも思うので、そのあたりをやろうかなと思っています。

ただ、今週から来週にかけてはもう色々予定が立て込んでいるので、その先2日位を使ってやろうかなと。その後また予定が立て込んでいるのでむむーん・・しばらく勉強のペースはセーブしたほうが良さげです。