はじめに
APU6を2台入手したので、SFPモジュールを介してLC光ケーブルで接続しつつ、Linux FoundationがホストするFast Data Project (FD.io)のVPPを試すことにしました。
英語でも資料が十分とはいえず、いろいろ問題に遭遇したので作業時のメモを残しておくことにします。
環境
- OS - Ubuntu 22.04
- Software - FD.io VPP v23.02
- Hardware - APU6 with SFP modules (10Gtek製 1.25G 850nm MMF) 2台
目標とする構成とVPPの特徴
構成は一般的なホームルーターと似ていますが、グローバルIPは静的に固定します。
このためIPoEやPPPoEは使いませんし、試していません。IPoEは問題ないと思いますが、PPPoEもPluginにpppoe_plugin.soが含まれているので何とかなりそうです。
VPPを使ったIPoE接続については別記事にまとめています。
FD.io VPP 24.02でフレッツ(IPoE)ホームルーターを自作した時のメモ
本家の資料にもHome gatewayを構築する設定例(s3-docs.fd.io)が掲載されていますが、ここで紹介されているグローバルIPを取得するためのDHCPクライアントは使用しませんので、それらの設定も省略しています。
192.168.110.0/24には実験用のサーバーなどを配置する予定で、うまく動作すればAPU6#01にdnsmasqを導入し、192.168.110.10経由でネットワーク全体の管理を行います。
APU6#01で稼動するsshdやnginxなどのOS配下のプロセスは、VPPの管理下にあるGlobal IP(xxx.xxx.xxx.xxx)には接続できないため、kernelの配下にあるtap0(192.168.110.10)を通してVPP管理下にあるL2ブリッジ(bvi1)を経由して192.168.110.0/24の物理ネットワークとインターネットに接続します。
VPPが特殊なのはUserlandで動作しているため、デバイスが一般的なカーネル管理下になくiptablesなどでパケットをフィルタリングすることはもちろん、ip addr
コマンドでデバイス情報を表示することすらできない点にあります。
VPPが動作した状態ではip addr
コマンドはloとtap0デバイスのみを表示します。
tap0はuserlandで動作するVPPとカーネルが管理する一般的なネットワーク通信を仲介する役割を担当します。OSからはグローバルIP(xxx.xxx.xxx.xxx)がみえないため、運用に必要なパッケージの更新などでインターネットに接続する際にはtap0インタフェースを通じて、NAPTによってVPPのグローバルIPを通り外部と通信することになります。
sshdなどのdaemonは、tap0デバイスの192.168.110.10/24にbindし動作します。外部からsshdにアクセスしたい場合にはVPPの設定でグローバルIPからtap0にポートフォワードすることになります。Webサーバーも同様でVPPからフォワードされたパケットをtap0を通じて受け取ります。
この時動作するサーバーは実際には0.0.0.0にbindしないと起動順によっては192.168.110.10/24が未設定となって起動できずエラーになる可能性があります。
もし192.168.110.10にbindするように設定したい場合には起動に失敗しても再起動するよう、適当な監視スクリプトをcrontabなどに指定しておくのが良いでしょう。
導入方法
公式ドキュメントの手順に従って、リリース版のdebパッケージを導入しています。
installスクリプトは、/etc/apt/sources.list.d/ と /etc/apt/keyrings/ に、それぞれファイルを配置します。ansibleで自動化する際には、このファイルの内容をコピーして配置すればインストールスクリプトを実行する必要はありません。
$ dpkg -l | grep vpp
ii libvppinfra 23.02-release amd64 Vector Packet Processing--runtime libraries
ii vpp 23.02-release amd64 Vector Packet Processing--executables
ii vpp-plugin-core 23.02-release amd64 Vector Packet Processing--runtime core plugins
ii vpp-plugin-dpdk 23.02-release amd64 Vector Packet Processing--runtime dpdk plugin
参考資料
- 公式ガイド - VPP as a Home Gateway
- メーリングリスト Re: [vpp-dev] nat44 not working under Ubuntu 22.04
- 公式ガイド - NAT44-ED: NAT44 Endpoint Dependent
この他の参照した資料
- https://www.slideshare.net/tetsusat/vpp-147934534 (FD.io VPP事始め)
- https://haryachyy.wordpress.com/tag/vector-packet-processing/ (Denys Haryachyy Blog)
- https://blog.apnic.net/2020/04/17/kernel-bypass-networking-with-fd-io-and-vpp/
- https://metonymical.hatenablog.com/entry/2020/08/10/005408
基本的な設定
基本的には公式サイトのガイドと同様ですが、微妙な書き方の違いで挙動が異なるため、検証は慎重に行う必要がありました。
unix {
nodaemon
log /var/log/vpp/vpp.log
full-coredump
cli-listen /run/vpp/cli.sock
startup-config /etc/vpp/local.cfg
}
api-trace {
on
}
api-segment {
gid vpp
}
socksvr {
default
}
cpu {
main-core 1
corelist-workers 2-3
}
dpdk {
dev default {
num-rx-desc 512
num-tx-desc 512
}
no-multi-seg
no-tx-checksum-offload
dev 0000:01:00.0 {
name enp1s0
num-rx-queues 2
num-tx-queues 2
}
dev 0000:02:00.0 {
name enp2s0
num-rx-queues 2
num-tx-queues 2
}
}
plugins {
plugin default { disable } ## default
plugin nat_plugin.so { enable }
plugin dpdk_plugin.so { enable }
}
dpdk{}のdevに指定するのはpci-eのアドレスです。lshw -c network
などで表示させることができます。
comment { for local network }
bvi create instance 0
set int l2 bridge bvi0 1 bvi
set int ip address bvi0 192.168.110.1/24
set int state bvi0 up
comment { Add more inside interfaces as needed ... }
set int l2 bridge enp1s0 1
set int state enp1s0 up
comment { for enp2s0, global ip }
set int ip address enp2s0 xxx.xxx.xxx.xxx/24
set int state enp2s0 up
ip route add 0.0.0.0/0 via xxx.xxx.xxx.1 enp2s0
comment { host-stack access, tap0 }
create tap host-if-name tap0 host-ip4-addr 192.168.110.10/24 host-ip4-gw 192.168.110.1
set int l2 bridge tap0 1
set int state tap0 up
comment { Configure NAT }
nat44 plugin enable sessions 63000
nat44 add interface address enp2s0
set interface nat44 in bvi0
set interface nat44 out enp2s0 output-feature
nat44 forwarding enable
comment { allow inbound ssh to the 22 }
nat44 add static mapping local 192.168.110.10 22 external enp2s0 22 tcp
後述しますが、nat44のルールを書く際には公式ガイドなどにある in, out を1行にまとめる書き方では動作しませんでした。よくみると公式ガイドではoutput-feature
は省略されていたりするので、公式ガイドが完全に間違っているわけではありません。
意味を考えながら必要な手続きを順序良く記述する必要がある点に注意が必要です。
戸惑った事や遭遇した不具合
検索して出てくる資料そのものが少かったり、ターゲットがvSwitchのような用途だったり、使用したバージョンが古く、nat { endpoint-dependent }
のような23.02ではunknownとしてエラーになる設定を含んだりしました。
例えば、Denys Haryachyy Blogには次のようなNAT設定が掲載されていますが、これは現在(v23.02)ではまったく動作しません。
nat {
endpoint-dependent
translation hash buckets 1048576
translation hash memory 268435456
user hash buckets 1024
max translations per user 10000
}
またunix{}内部(あるいはstartup-configに指定されるファイル)に記述する、次の設定も23.02ではエラーになります。
nat addr-port-assignment-alg default
この他に遭遇した事柄について、まとめています。
ssh接続での作業が難しい
OSをインストールした直後には、ip addr
で表示される一般的なネットワークデバイスを利用しなければいけません。
この初期状態からVPPを導入するなどのネットワーク関連の設定を行うことは、通信が切断されるなど作業が継続困難になる可能性がそれなりにあります。
BMCカードなどが利用できれば問題ないのですが、今回利用したAPU6はCP2104チップを利用したシリアル接続用のUSBポートが準備されています。Windowsではどうも動作が不安定だったり、VMware上のLinuxホストに接続できないなどの不具合が頻発したので、カーネルにドライバが組込まれているlinuxを利用しました。Linuxホストでも度々通信が不安定になっています。
それでもWindowsよりはいくらか安定しているので、他のLinuxホストにssh経由で接続した後に、sudo screen /dev/ttyUSB0 115200
といった方法でシリアル端末にアクセスしています。
UbuntuをホストとしてVPPを利用する場合にはホームディレクトリにnetplanの設定ファイルを置いておいて必要に応じて、/etc/netplan/に配置して再起動やnetplan applyすることでカーネルとVPPの間を行き来しました。
作業環境については、BMCカードなどを利用するか、SSH接続可能な端末からシリアル経由でVPPが稼動するサーバーにアクセスする方法を検討・確認しておくことがお勧めです。
sudo vppctl show interface で目的のNICが表示されない
そもそもカーネル管理下にあって、netplanなどでIPアドレスなどが割り当てられているNICはVPPで扱うことができません。
様々な例でもipコマンドでlink downさせてからVPPの設定を始めています。
ubuntu 22.04 server版ではnetplanの設定ファイルを削除したりsystemctlからvpp.serviceをdisableにするなどして切り替えれば問題ないのですが、Desktop版などを利用するとnetworkmanagerなどが動作してしまいかなりはまるポイントだろうと思います。
OS管理下にないインタフェースは自動的にVPPに表示されるので、MACアドレスやPCIのバス番号などで識別することが必要です。
$ sudo lshw -c network
$ sudo lshw -c network -businfo
どのpluginを読み込めば良いかわからない
nat関係と思われるpluginには次のようなものがあります。
$ $ ls /usr/lib/x86_64-linux-gnu/vpp_plugins | grep nat
cnat_plugin.so
crypto_native_plugin.so
nat44_ei_plugin.so
nat64_plugin.so
nat66_plugin.so
nat_plugin.so
pnat_plugin.so
一般的なgatewayを構築するだけであれば、nat_plugin.so だけで十分です。
マニュアルなどをみる限り、どのpluginにどんな命令が定義されているかは、nm -D --defined-only
やstrings
などのコマンドで個別に確認するのが確実そうです。
nat44が有効にできない
やや古い公式のHome Gatewayの設定例を参考にしたところ、以下のようなエラーメッセージが表示されてしまいました。
Mar 25 22:49:20 ub2204 vnet[693]: nat: nat44 is disabled
Mar 25 22:49:20 ub2204 vnet[693]: nat44 add interface address: add enp2s0 address failed
参考資料にも挙げているメーリングリストの記事から、nat44 plugin enable
が抜けている場合のあることが分かります。
利用するVPPのバージョンに合わせたNAT44-EDのドキュメント(https://s3-docs.fd.io/vpp/23.02/developer/plugins/nat44_ed_doc.html)を確認することが正確な情報を入手するために必要です。
また、Debian/Ubuntuのdebパッケージのデフォルト設定はpluginが読み込まれないようになっているため、nat関連のパッケージを読み込む必要があります。
plugins {
plugin nat_plugin.so { enable }
}
nat_plugin.soにはnat44関連の命令がほとんど含まれています。
最終的にはpluginを追加し、次のような命令をnat44の設定を開始する先頭に配置して解決しました。
nat44 plugin enable sessions 63000
nat関連の設定は、nat44 plugin enable 〜を実行してから記述しましょう。
NATを通じて、DNSはlookupできるのにTCP接続ができない
NATを正しく構成したようにみえても、TCPでの通信が成功しませんでした。
DNSは参照できており、UDPの通信で単独パケットのやりとりは問題ないようです。
(192.168.110.2) $ dig www.ibm.com
...
;; ANSWER SECTION:
www.ibm.com. 183 IN CNAME www.ibm.com.cs186.net.
www.ibm.com.cs186.net. 1143 IN CNAME outer-global-dual.ibmcom-tls12.edgekey.net.
outer-global-dual.ibmcom-tls12.edgekey.net. 14200 IN CNAME e7817.dscx.akamaiedge.net.
e7817.dscx.akamaiedge.net. 16 IN A 104.78.105.90
...
UDPのパケットは問題ないですが、TCPでの通信は成功しません。
(192.168.110.2) $ curl -vv www.ibm.com
* Trying 104.78.105.90:80...
* Connected to www.ibm.com (104.78.105.90) port 80 (#0)
> GET / HTTP/1.1
> Host: www.ibm.com
> User-Agent: curl/7.81.0
> Accept: */*
>
## このまま停止
自分が管理できるWebサーバーに接続させてみると、SYNが届いていて、ACKを返しているのに届いていないようです。TCPのハンドシェイクで問題が起るのはセッション管理がおかしいようですが、関連した話題はメーリングリストなどではみつけられませんでした。
$ sudo tcpdump -n -vvv -i ens1 port 80
tcpdump: listening on ens1, link-type EN10MB (Ethernet), capture size 262144 bytes
12:39:58.980997 IP (tos 0x0, ttl 63, id 40517, offset 0, flags [DF], proto TCP (6), length 60)
xxx.xxx.xxx.xxx.16961 > yyy.yyy.yyy.yyy.http: Flags [S], cksum 0x5bff (correct), seq 1576809263, win 64240
, options [mss 1460,sackOK,TS val 3377869175 ecr 0,nop,wscale 7], length 0
12:39:58.981073 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 60)
yyy.yyy.yyy.yyy.http > xxx.xxx.xxx.xxx.16961: Flags [S.], cksum 0x22b4 (incorrect -> 0x1d5f), seq 23420096
20, ack 1576809264, win 28960, options [mss 1460,sackOK,TS val 984925950 ecr 3377869175,nop,wscale 7], length 0
試行錯誤の結果、local.cfgファイルの書き方を変更することで解決しました。
set interface nat44 in bvi0 out enp2s0 output-feature
これを次のように変更すると問題は解消しました。
set interface nat44 in bvi0
set interface nat44 out enp2s0 output-feature
この違いについては追求しきれていません。→ 最後に原因を追記しました。
local.cfgの記述で気をつけるべき点
local.cfgファイルはstartup.confの中で、unix{}内部でstartup-configによって読み込むようにしている設定ファイルです。
この設定ファイルの書式はバージョンによって、かなり変更されているようです。
例えば、v:latestタグで表示される古い公式サイトでは次のような記述がありますが、これはv23.02では動作しません。
dpdk {
...
poll-sleep 10
}
ちゃんと公式ガイドを参照すると、これはunix{}のセクションに、poll-sleep-usec
として設定ができます。
unix {
...
poll-sleep-usec 100
}
このように現在のバージョンでは有効ではない記述がとにかく多い点を念頭にメーリングリストのアーカイブなどを検索する必要がありました。
さいごに
nat44のおかしな挙動はVMware Workstation上のVMのNICのドライバをvmxnet3に変更して検証しましたが、同様の挙動を示しました。確認のため23.02系列の最新版や23.06などのmasterブランチのパッケージで確認しても同様でした。
VPPを利用するユーザーの多くはスループットやvSwitchなどホスト内の複雑なルーティングを行いたい人達がまだ多いのかもしれませんが、nat44は実用上では重要かつ基本的なモジュールです。ドキュメントや利用例の報告が少ないのは、まだ従来の用途を置き換えるような目的では利用されていないからなのかもしれません。
しかしUserlandでルーティングが操作できることは、OSの制約からネットワークを分離できるためとても魅力的です。
OSが自身の更新のためにtapデバイスが必要だったりする点は面倒という印象を強く与えると思います。それでもメリットが大きいと思われますので、今後はこういった仕組みを使って個人的な実験に利用しているNAPT Gateway以外のサーバーも置き換えていくつもりです。
後日談 〜 nat44 in out output-feature を一行で書いて問題が起きた理由
すっきりしなかった、output-featureを含む行を、2行に分けると問題が収束した理由についての説明です。
問題が発生したものの、次のような設定にしたところ問題が収束したところまでは分かっていました。
set interface nat44 in bvi0
set interface nat44 out enp2s0 output-feature
これはgithubにホストされているVPPのコードを確認すると、内部では次のように1つの関数内で処理されています。
static clib_error_t *
snat_feature_command_fn (vlib_main_t * vm,
unformat_input_t * input, vlib_cli_command_t * cmd)
{
...
u32 *inside_sw_if_indices = 0;
u32 *outside_sw_if_indices = 0;
u8 is_output_feature = 0;
...
if (unformat (line_input, "in %U", unformat_vnet_sw_interface,
vnm, &sw_if_index))
vec_add1 (inside_sw_if_indices, sw_if_index);
else if (unformat (line_input, "out %U", unformat_vnet_sw_interface,
vnm, &sw_if_index))
vec_add1 (outside_sw_if_indices, sw_if_index);
else if (unformat (line_input, "output-feature"))
is_output_feature = 1;
...
一見すると、1行でも2行に分けても同様に処理されそうですが、その先のコードでは次のように分岐しています。
...
for (i = 0; i < vec_len (inside_sw_if_indices); i++)
{
sw_if_index = inside_sw_if_indices[i];
if (is_output_feature)
{
...
{
rv = nat44_ed_add_output_interface (sw_if_index);
}
...
}
else
{
...
{
rv = nat44_ed_add_interface (sw_if_index, 1);
}
...
抜粋が多くて分かりにくいですが、inデバイスについてもis_ouptput_feature
の有無によって呼ばれるコードが違います。
output-feature
を指定すると、そのデバイスがnat44_ed_add_output_interfaceに渡されますが、この挙動は、後半のoutside_sw_if_indices
でも同様に呼ばれます。
今回の場合はtap0と接続する内部のbvi0デバイスをnat44_ed_add_output_interface()を通じて登録することになるので、なんとなく外部IPもmasqueradeされそうな雰囲気です。
もう少し調べると確かに一行でin out output-feature
を書き並べた場合は、いわゆるヘアピンNATとも違い、外部から内部にアクセスする際のIPアドレスもenp2s0に付与されているIPアドレス(xxx.xxx.xxx.xxx)でmasqueradeされてしまっていました。どこからsshdにログインしても接続元は(xxx.xxx.xxx.xxx)になってしまいます。
失敗した時と、成功した時での$ sudo vppctl show int bvi0 features
コマンドの出力は次のように違いがあります。
ip4-local:
none configured
ip4-output:
ip4-sv-reassembly-output-feature
nat44-in2out-output-worker-handoff
ip4-multicast:
none configured
ip4-unicast:
ip4-sv-reassembly-feature
nat44-out2in-worker-handoff
ip4-local:
none configured
ip4-output:
none configured
ip4-multicast:
none configured
ip4-unicast:
ip4-sv-reassembly-feature
nat44-in2out-worker-handoff
bvi0のip4-output:
の部分が違います。
この失敗した時の表示は、enp2s0の外部IFに設定されているものと同じでした。
ip4-local:
none configured
ip4-output:
ip4-sv-reassembly-output-feature
nat44-in2out-output-worker-handoff
ip4-multicast:
none configured
ip4-unicast:
ip4-sv-reassembly-feature
nat44-out2in-worker-handoff
このようにnat44の設定をする際には、output-featureを設定する場合には自動的にoutに指定したデバイスにだけ反映されるということはないので、in,outの設定は分けて記述するようにするのが良さそうです。
ここまでで、挙動が違う理由については判明したかなと思います。
後日談2 〜 VPPを利用する際のメモリサイズとCPUのキャパプラについて
APU6上でVPPを動作させたまではよくて、その分には問題ないのですが、4GBのメモリでは少し窮屈だと感じるようになりました。
起動から一定時間経過すると平常時は800MB程度の空きメモリはありますが、venvを経由してkubesprayに必要なパッケージを導入しようと、pip install -r requirements.txt
を実行すると100MB以下まで空きメモリが逼迫してしまいます。
APU6がVPP専用ノードであれば問題ないのですが、nginxやdnsmasqなどいくらか追加のワークロードも稼動させたいため、メモリに余裕がない状態は少し厳しいです。
またtopコマンドで確認するとvpp_mainプロセスのCPU使用率は200%を越えている状況です。AMD GX-412TC CPUは4スレッド動作させることを考えるとまだ余裕はありますが、全体的なパフォーマンスには余裕がない状態が続いています。
PC Engines社から2023年6月末でGX-412TCの受注停止がアナウンスされています。約1年程度の需要を満たすだけの最終オーダーを行った上で製造を継続する意向が表明されていますが、後継製品がアナウンスされていない状況では今後はProtectli社製のFanlessサーバーなどを購入すると思います。
Protectli社の製品は、いくらか高価でCPU性能は価格に比例して変化しますが、APUよりは高性能で、メモリは確実に増やすことができます。とりあえずVP2420を購入して32GBほどメモリを搭載して同様の構成でテストする予定です。
以上