情報畑でつかまえてロゴ
本サイトは NTTテクノクロスが旬の IT をキーワードに
IT 部門が今知っておきたい最新テクノロジーに関する情報をお届けするサイトです

DPDKアプリケーション作成 パケット処理編 ~DPDK入門 第8回~

前回はDPDKアプリケーションでパケット処理を作成するために必要な初期設定について解説しました。今回はパケット処理の作成について解説します。

はじめに

こんにちは、NTTテクノクロス株式会社の寺尾です。
前回から引き続きDPDKのアプリケーションを作成していきます。よろしくお願いします。

パケット処理の作成

前回の記事で、DPDKでNICを扱えるようにする準備をしました。残りは"パケットの受信と送信”の処理が必要になります。今回作成する処理のイメージを以下に示します。



今回は「指定のポートからパケットを受信する処理」と、「受信したパケットを指定のポートに送信する処理」を作成します。 DPDKアプリケーションでパケット処理を扱う際、余計なコピー処理を行うことなくパケットバッファを直接操作することが重要です。 今回作成するシーケンスのイメージを以下に示します。


パケット受信と送信処理

今回はポート番号0から受信パケットを取得し、取得したパケットを同じポートに送信するパケット処理を作成しました。rte_eth_rx_burst()でパケット受信を、rte_eth_tx_burst()でパケット送信を行います。

▼パケット受信と送信処理
struct rte_mbuf *pkts[32];
・・・
while (1) {
  nb_rx = rte_eth_rx_burst(0, 0, pkts, 32);
  if (nb_rx <= 0)
    continue;
  nb_tx = rte_eth_tx_burst(0, 0, pkts, nb_rx);
・・・
}

rte_eth_rx_burst()の返り値は「取得した受信パケット数」です。プログラム例では、受信パケットがなかった(戻り値が0)の場合すぐに再度パケット受信の確認を行い、受信パケットがある場合はそのパケットを送信しています。また、rte_eth_tx_burst()の返り値は「送信に成功したパケット数」となります。

rte_eth_rx_burst()とrte_eth_tx_burst()のパラメータに関して解説します。
- 第1パラメータは、ポート番号を指定します。
- 第2パラメータは、パケットを取得、または送信するキュー番号を指定します。
- 第3パラメータは、取得もしくは送信するパケットの格納先(※1)を指定します。
- 第4パラメータは、一括で操作(※2)するパケット数の上限値を指定します。今回は受信するパケットの上限を"32"、送信は受信で取得したパケット数を指定しています。

※1 パケットの格納先ついて
パケットの実体はDPDK内で管理されており、DPDKアプリケーション側からは間接的に参照する形となります。

※2 一括処理について
出来るだけ効率良く処理するため、個々の処理を1つ1つ行うよりもなるべく一括で処理を行うようにします。このことはDPDKの公式ページのPMDの説明(※3)でも触れられていますので、詳しく知りたい人は参照してください。 一括で処理するパケット数を大きくした場合、個々のパケット処理が遅延することがありますので、アプリケーションの特性に合わせて調整下さい。

※3 Poll Mode Driver (10.2. Design Principles)
https://doc.dpdk.org/guides/prog_guide/poll_mode_drv.html

【APIリファレンス】

rte_eth_rx_burst
引数 in/out パラメータ 補足
uint16_t input パケット受信監視対象のポート番号 -
uint16_t input パケット受信監視対象のキュー番号 -
struct rte_mbuf ** output 受信パケットの格納先 パケットはポインタで間接的に参照する
uint16_t input 取得するパケットの最大数 -
戻り値 説明
uint16_t 整数 取得した受信パケット数
ヘッダファイル rte_ethdev.h

rte_eth_tx_burst
引数 in/out パラメータ 補足
uint16_t input パケット送信先のポート番号 -
uint16_t input パケット送信先のキュー番号 -
struct rte_mbuf ** input 送信パケットの格納先 パケットはポインタで間接的に参照する
uint16_t input 送信するパケット数 -
戻り値 説明
uint16_t 整数 送信に成功したパケット数
ヘッダファイル rte_ethdev.h

パケットバッファの解放

パケットバッファはパケット受信時にPMDが確保します。確保するためのメモリは初期化処理時にrte_eth_rx_queue_setup()で設定していますが、メモリリソースは有限であるため不要となったパケットバッファは解放する必要があります。

パケット送信に成功した場合、DPDKライブラリ内でパケットバッファが自動的に解放されます。しかし、パケット送信に失敗した場合は、そのままでは解放されません。このパケットバッファを放置したまま送信失敗が続くと、そのうち受信パケットに割り当てるメモリが枯渇します。このため、送信に失敗したパケットは再送する、あるいは解放するなどの考慮が必要になります。今回作成したアプリケーションでは「送信に失敗したパケットは全て解放する」ようにしています。パケットバッファの解放はrte_pktmbuf_free()で行います。

▼送信失敗パケットの解放
// - nb_rx=受信したパケット数
// - nb_tx=送信成功したパケット数
nb_tx = rte_eth_tx_burst(0, 0, pkts, nb_rx);
if (nb_tx < nb_rx)
  for (i = nb_tx; i < nb_rx; i++)
    rte_pktmbuf_free(pkts[i]);

ここまでで、パケット処理は完了です。

【APIリファレンス】

rte_pktmbuf_free
引数 in/out パラメータ 補足
struct rte_mbuf * input 解放したいパケットバッファへのポインタ -
戻り値 説明
void - -
ヘッダファイル rte_mbuf.h

DPDKアプリケーションの動作確認

それでは、作成したDPDKアプリケーションの動作確認を行ってみましょう。動作確認で利用する環境はDPDKのパケット処理 ~DPDK入門 第2回~と同様です。また、DPDKアプリケーションを動作させるための各設定は実施しておきます。 まず、作成したソースコード(main.c)をホストBのホームディレクトリ配下に配置します。

hostb$ ls -la ~/ | grep main.c
-rw-rw-r-- 1 xxxx xxxx 8657 6月 5 10:28 main.c

次に既存のHello Worldサンプルアプリケーションのソースコードを、作成したソースコードに置き替えます。

hostb$ cd dpdk-<version>
hostb$ export RTE_SDK=`pwd`
hostb$ export RTE_TARGET=x86_64-native-linuxapp-gcc
hostb$ mv examples/helloworld/main.c examples/helloworld/main_org.c
hostb$ cp ~/main.c $RTE_SDK/examples/helloworld/.

アプリケーションの実行と動作確認

動作確認には以前使っていたncコマンドやtcpdumpコマンドを使って説明します。使い方が分からない方は DPDKのパケット処理 ~DPDK入門 第2回~で解説していますので参照下さい。

今回はホストAのeth0から送信されたパケットをホストBのDPDKアプリケーションが受信して折り返す構成となっています。また、パケット送信ツールにncコマンド、パケット受信確認にはtcpdumpコマンドを使います。前回と違うところとしては、ホストBが折り返したパケットはホストAのeth0に戻ってくるはずなので、ホストAのeth0でtcpdumpを使用して確認します。今回の確認構成イメージを以下に示します。



それでは作成したDPDKアプリケーションをコンパイルします。以前とやり方は同じです。

hostb$ cd $RTE_SDK/examples/helloworld/
hostb$ make
CC main.o
LD helloworld
INSTALL-APP helloworld
INSTALL-MAP helloworld.map

続いてDPDKアプリケーションを起動します。起動方法について、作成したDPDKアプリケーションでは複数のコアを使わないため、-c で CPUのコア0のみを指定しています。

hostb$ sudo ./build/helloworld -c 0x1
EAL: Detected 32 lcore(s)
EAL: Detected 2 NUMA nodes
EAL: Multi-process socket /var/run/dpdk/rte/mp_socket
EAL: Probing VFIO support...
EAL: PCI device 0000:04:00.0 on NUMA socket 0
EAL: probe driver: 8086:1521 net_e1000_igb
EAL: PCI device 0000:04:00.1 on NUMA socket 0
EAL: probe driver: 8086:1521 net_e1000_igb
EAL: PCI device 0000:05:00.0 on NUMA socket 0
EAL: probe driver: 8086:154d net_ixgbe
EAL: PCI device 0000:05:00.1 on NUMA socket 0
EAL: probe driver: 8086:154d net_ixgbe

Initialize DPDK done.

Initializing port 0
Please wait for the port 0 to LinkUp... done.

DPDKアプリケーションが正常に動作していれば、NICの0番(ホストBのeth0)が受けたパケットはそのまま折り返され、ホストAのeth0に到達するはずです。このことを確かめるため、ホストAのeth0に向かってUDPパケットを送信します。
まずホストAのeth0でtcpdumpコマンドを実行し、ホストBで折り返したパケットを確認できるようにしておきます。

hosta$ sudo tcpdump -i eth0
・・・

それではホストAのeth0からパケットを送信します。送信方法はDPDKのパケット処理 ~DPDK入門 第2回~と同じなので、細かい説明は割愛します。

hosta$ sudo arp -s 192.168.11.100 aa:aa:aa:bb:bb:bb
・・・
hosta$ nc -u 192.168.11.100 5000
test

UDPパケットを送信すると、以下のように非常に短い間隔でUDPパケットが2つ表示されます。先に表示されている方はホストAから送信したUDPパケット、後に表示されている方はホストBで折り返したUDPパケットであるため、期待通りの結果となりました。

hosta$ sudo tcpdump -i eth0
・・・
13:34:09.735928 IP 192.168.11.1.56811 > 192.168.11.100.5000: UDP, length 5
13:34:09.736028 IP 192.168.11.1.56811 > 192.168.11.100.5000: UDP, length 5

今回作成したソースコードについて

最後に、今回作成したアプリケーションのソースコードを公開します。「DPDKアプリケーションの動作確認」に記載したように動作しますので、試してみてください。(※4)

サンプルコード(第8回)

※4 サンプルコードご利用の際の注意事項
本ソースコードはあくまでサンプルコードです。本ソースコードを利用して発生した損害について、当社は一切の責任を負いません。

おわりに

前回から今回の記事でDPDKを利用したアプリケーションの基本的な作成方法を解説させていただきました。次回は開発環境に関して解説をしたいと思っています。ご覧頂きありがとうございました。

連載シリーズ
DPDK入門
著者プロフィール
寺尾 智之
寺尾 智之
長年NTTの通信基盤となるネットワーク関連の仕事に従事。 最近では仮想化技術が主流化しており、最新動向を追従する日々。 DPDKに関してはニッチな領域のためか仲間が増えないと感じており、ブログを通した仲間づくりを開始。 横浜生まれだが、巨人ファン。たまに東京ドームに観戦に行くことが楽しみ。