MininetでSRv6 L3VPNを動かす

Mininet 1 の中でSRv6 L3VPNを動かす実験をやってみた。 スクリプトはこちら。 ひとまず図のような小さな構成で動かすことができたのでここで紹介したい。 2台のルータ(r1とr2)がSRv6によるEncap/Decapを担当しており、 r1-r2間でL3VPNに関する情報をeBGPで交換する。 r1とr2はそれぞれ2つのVRF(vrf10とvrf20)を持っており、 VRFごとにテナントが分けられている(Tenant10とTenant20)。 もちろんテナントごとにL3の疎通性はなくIPレンジの重複は許されているので、 ここでは同じPrefixを割り当てている。 r1とr2ではBGPデーモンとしてFRRを使っている。設定の一部を抜粋するとこんな感じ。 FRRの設定ファイル(frr.conf)全体についてはmininetlab 2 に記載している。 FRR本体のテストコード 3 が非常に簡潔で綺麗にまとまっているので、それを参考にした。 FRRではSRv6 L3VPNの開発は活発に行われているので、できれば新しいバージョンをおすすめする。 ここではFRR 8.5を使った。 mininet> r1 vtysh -c "show running-config" # 一部抜粋 router bgp 65001 bgp router-id 203.0.113.1 bgp default ipv4-vpn bgp default ipv6-unicast bgp bestpath as-path multipath-relax no bgp network import-check neighbor r1-eth0 interface remote-as external ! segment-routing srv6 locator default exit exit ! router bgp 65001 vrf vrf10 bgp router-id 203....

July 7, 2023

TRexのラッパーを作ってみた

まずはTRexの紹介から。 TRex 1 はソフトウェア実装のトラフィックジェネレータで、 Stateful/Stateless の2モードをサポートしている。 Statelessは状態を持たない対象DUT(Device Under Test)宛に パケット列を生成するためのモードで、 スイッチングやルーティングの性能計測ツールとして利用できる。 TRexは多機能ではあるが、個人的なニーズとしては単純なTCP/IPのパケット列を そのサイズを変えながら生成したいことが多いのでTRexのラッパーとしてautotrex 2 を作ってみた。 autotrexは自動的にベンチマークの実行・集計をするためのもので、 例えば成果物として下記のような図を簡単に生成できる。 ここでは簡単に使い方を記録しておく。 autotrex では trex_cfg.yaml (TRexの世界でよく使われる設定ファイル)に記載した2つのポートのうち、 1つ目のポートが送信、2つ目のポートが受信を担当する。 送信・受信それぞれのパケット数をカウントし、その誤差がある閾値 (例えば0.01%)以下であるような最大のパケットレート (Packet Per Second、PPS)を二分探索で求める。 もちろん生成したいパケット列は自分で下記のように自由に設定できる。 私がよく使うものはautotrexリポジトリ同梱されていて 3、 簡単に使えるようになっている。 # tcp_1pkt.py から抜粋 pkt=Ether()/IP(src="16.0.0.1", dst="48.0.0.1") / TCP(dport=12, sport=1025)/(payload_size*'x') ./run.sh tcp_1pkt.py のようにパケット列を記載したPythonファイル tcp_1pkt.py を 引数に与えて run.sh コマンドを発行すると、自動的にサイズを 変えながらパケット生成が行われる。 一連のベンチマーク実行が完了すると、成果物としてパケットレート、L1・L2のスループットを表すcsvファイルとpngファイルが出力される。 どのようなパケットが生成されるかについては./simulate.sh tcp_1pkt.pyコマンドで 事前に知ることができる。 独自にパケットを生成したい場合には便利かもしれない。 性能が欲しい環境では下記のように trex_cfg.yaml でCPU情報を設定する。 経験的にmaster_thread_id、latency_thread_id、threads にはユニークなCPUを 割り当てた方が高い性能を得られる。詳しくはドキュメント 4 に説明がある。 platform: master_thread_id: 0 latency_thread_id: 5 dual_if: - socket: 0 threads: [1, 2, 3, 4] 以上、個人的に使っているツールの紹介。おしまい。...

June 9, 2023

Intel NUC 12 Pro で仮想環境をつくる

開発機として Intel NUC 12 Pro 1 を買って Proxmox VE 2 を入れてみた。 これまでは古い Thinkpad を開発機としていたので、快適になったかなと思う。 スペックはこんな感じ。 メモリとディスクは重視したいところだったので多めにした。 一方でコア数はそこまで拘らないのでi3のモデルとした。 vPro 搭載モデルも検討したが、流通量が少なそうなこと、仮想化すればリモートでの管理操作の頻度は減るだろうということ、から見送った。 NUC:Intel NUC12WSHi3(Core i3-1220P) 電源コード:サンワサプライ KB-DM3S-1 メモリ:Team SO-DIMM DDR4 3200MHz PC4-25600 32GBx2 ストレージ:Crucial CT2000P3PSSSD8JP 2TB M.2 PCIe4.0 元々はESXi 8を使おうかと思っていたが、 “Shutting down firmware services” のエラーに苦しみ結局解決できず諦めることにした。 ちなみにPコア・Eコアが混在することによる問題もいくつか報告されているが、 それに対する知見はそれなりに広まっていて 3 4 私の環境でも同じように解決できた。 ESXi 7についてはいくつか 12 世代 Intel NUC での動作事例が見つかったが、コミュニティドライバ?が必要なので面倒に感じてしまい諦めた。 残りの候補として cockpit-machines と Proxmox VE を考えていた。 この中でもできるだけ手軽に使いたかったので Proxmox VE を選択した。 Proxmox VEのインストール手順については特筆するものはなく、 ダイアログにしたがってやっていけば自然にインストールできた。 まだまだ触れていないが、第一印象はなかなか快適で良さそうだ。 Intel NUC12WSHi3 ↩︎...

May 1, 2023

LUA4-U3-AGTE-NBK ドライバのインストール

特権コンテナで遊んでいたら /lib/modules 配下を壊してしまったようだ。 よくわからないがこのマシンから外部に出ていくときに、疎通したりしなかったりする。 イーサネットをUSBタイプAとして受けるアダプタ BUFFALO LUA4-U3-AGTE-NBK 1 を使っていて、 それに対応するドライバが怪しい。 ログはこんな感じ。 ubuntu2004thinkpad:~$ dmesg IPv6: ADDRCONF(NETDEV_CHANGE): enx9096f349a025: link becomes ready usb 3-1: USB disconnect, device number 45 ax88179_178a 3-1:1.0 enx9096f349a025: unregister 'ax88179_178a' usb-0000:00:14.0-1, ASIX AX88179 USB 3.0 Gigabit Ethernet ax88179_178a 3-1:1.0 enx9096f349a025: Failed to read reg index 0x0002: -19 ax88179_178a 3-1:1.0 enx9096f349a025: Failed to write reg index 0x0002: -19 ax88179_178a 3-1:1.0 enx9096f349a025 (unregistered): Failed to write reg index 0x0002: -19 ax88179_178a 3-1:1....

March 31, 2023

vhost-userのネゴシエーション

はじめに gokvmをvhost-userに対応させるにあたり、初期化部分について調査したのでメモとして残しておく。 QEMUのドキュメントVhost-user Protocol 1 に詳しくまとまっているが、 実際に動かしてみないことには分からない部分(例外処理だったり、リクエストの順番だったり、ログの落ち方だったり) もあると思うので動かしてみた。 色々試行錯誤してみたものの、結局 QEMU と DPDK の2つだけで手軽に試すことができた。 ここでは QEMU をサーバモード、DPDK をクライアントモードで動かした。 サーバモードが vhost-user 用の Unix Domain Socket の生成、クライアントがその Socket への接続を担当する。 dpdk-skeleton のビルド DPDK にはいくつかサンプルプログラムが用意されているが、ここでは dpdk-skeleton を利用することにした。 私の使っているディストリビューションではそれは DPDK パッケージに同梱されていなかったので、以下でビルドした。 $ git clone git@github.com:DPDK/dpdk.git $ cd dpdk $ meson setup -Dexamples=skeleton build $ cd build $ ninja $ file ./examples/dpdk-skeleton ./examples/dpdk-skeleton: ELF 64-bit LSB shared object, x86-64, ... 仮想マシンの起動 軽量なVMイメージであるCirrosをQEMUからブートさせた。 ここでpath=$HOME/vhost-net0 がUnix Domain Socket に対応する。 logfile=$HOME/vhost-net0....

March 8, 2023

自作VMM u-rootベースのinitrd

はじめに gokvm開発 1 2 3 4 5 6 の続き。 前回までに紹介したとおり virtio-blk と virtio-net に対応したことで、仮想マシンが外部とIOを通してやり取りができるようになった。 今回は initrd を busybox ベースから u-root ベースへと変更したので、それについて述べていく。 0d89a47f u-rootベースの initrd の導入 Go言語で作られたVMMには、同じくGo言語で書かれた initrd が相応しいのではないかということで、Pull Requestをもらった。 1コマンドで成果物を生成でき、busyboxと比べると手順が少なく簡単な印象を受けた。 cb504d85 u-rootベースのinitrdをデフォルトとする u-rootによるinitrdをしばらく触ってみると自分のやりたいことはこなせるだろうという感触を持ったので、デフォルトとした。ただ、busyboxでは特に意識せずできていたことが u-root ではできないことがあった。例えば以下のもの。 ctrl-lやctrl-eでシェル内カーソル移動を行うために、clearやticコマンドに加えて terminfo ファイルが必要だった。 ゲストの起動時に、NIC・ファイルシステムの初期化やHTTPサーバの起動のために、それを記載したスクリプトファイルを /bin/uinit に配置したが、デーモンが途中でkillされるような挙動になってしまった。init関連の挙動に対する自分の理解が甘いのだと思う。しょうがないのでワークアラウンドとして .bashrc に記載した。 終わりに この他にもいくつかリファクタリングを実施した。 今回はVMMらしい変更はなかった。今後はマイグレーションをやっていきたい。 KVMを使ったVMMを自作してLinuxを起動するまでの記録 ↩︎ KVMを使ったVMMを自作してLinuxを起動するまでの記録2 ↩︎ KVMを使った自作VMMのSMP対応 ↩︎ 自作VMMの PCI デバイス対応 ↩︎ 自作VMM の virtio-net 対応 ↩︎ 自作VMM の virtio-blk 対応 ↩︎

June 13, 2022

自作VMM の virtio-blk 対応

はじめに gokvm開発 1 2 3 4 5 の続き。 前回の virtio-net 対応に引き続いて、virtio-blk に対応した。 virt queueのデータ構造や挙動はそのまま流用できる。 この辺り Virtio はうまく設計されているなと感動する。 7389ff59 カーネルコンパイルオプションの調整 ゲストカーネルからファイルシステムを経由してブロックIOを実現するにあたって、以下のオプションを有効にした。 CONFIG_VIRTIO_BLK=y CONFIG_XFS_FS=y CONFIG_EXT3_FS=y CONFIG_EXT4_FS=y 4f4bbb78 virtio-blkの実装 さて、それでは本題である virtio-blk の実装に移っていく。 virtio-blk の挙動は virtio-net のものとほとんど同じなので、もし前回のブログを読んでいなければ、そちらを先に読むことをお勧めする。 差分はキュー数とdescripterテーブルのエントリが指す先のデータ構造だけである。 virtio-net では送受信のため2つのキューを必要としたが、virtio-blk の場合には 1つのキューで読み書きを実現する。 これはディスクへの読み書きはどちらもOS側からの発行となるため、外部割り込みを受ける必要がないためである。 descripterテーブルエントリが指すデータ構造は、以下のように3つのエントリがLinked Listの要領で繋がっている 6 。 1つ目のエントリが指すデータ構造は blkReq であり、typeフィールドが1なら書き込み、0なら読み込みを意味する。 sectorフィールドがディスクの先頭からのオフセットを意味する。 1セクタは512バイトなので、仮想ディスク用ファイルの sector x 512 バイト目から読み書きすることを意味する。 type blkReq struct { typ uint32 _ uint32 sector uint64 } 2つ目のエントリが実データを指す。ここに実際に読み書きしたいデータをバイナリで格納する。 3つ目のエントリがステータスである。エラーが発生した場合には0以外の数値を書き込む。 その他 Virt Queue の初期化方法や Avail Ring、Used Ring の使い方は virtio-net と全く同じ。...

April 12, 2022

自作VMM の virtio-net 対応

はじめに gokvm開発 1 2 3 4 の続き。 最近の一連の開発によって、gokvm 上のVMに virtio-net によって仮想NICを提供することができた。 ネットワーキングのサポートは当初の目標の一つだったので、達成感がある。 この対応によって gokvm 上のVMはホスト(あるいはソフトウェアスイッチを経由して外部)との間で通信できるようになった。 WEBサーバを提供したり、SSHでログインできたり、と出来ることの幅が広がる大きな変更だと思う。 例によって、重要なコミットを抜き出して振り返りたい。 c5217550 Virt Queue データ構造の追加 そもそも Virt Queue とは何なのか。 Virt Queue は ゲスト・ホスト間におけるデータのやり取りに使うリング構造のキューを意味する。 例えば送受信でそれぞれ1つのキューを使うナイーブな virtio-net の場合には、 送受信それぞれ1つの Virt Queue (全体で合わせて2つのVirt Queue)が必要になる。 もちろん、マルチキューをサポートする場合やコントロールキューをサポートする場合には、さらに Virt Queue が必要になる。 1つの Virt Queue は Descripter Table、Avail Ring、Used Ring から構成される。 取り扱いたいデータのアドレスと長さを1つのディスクリプタとしてまとめ、それをテーブル状に並べたものが Descripter Table である。 Avail Ring と Used Ring は似ていて、どちらもディスクリプタのIDをゲスト・ホスト間で 伝え合うために利用される。 方向も決まっていて、Avail Ring がゲストからホスト宛、Used Ring がホストからゲスト宛 となる。 ちなみに virtio 仕様 5 の中では、ゲストをdriver、ホストをdevice と表現している。...

March 18, 2022

自作VMMの PCI デバイス対応

はじめに gokvm開発 1 2 3 の続き。 gokvm 上のVMからPCIデバイスを取り扱えるよう開発を進めてきた。 道のりは長いだろうが、最終的には virtio-net を経由して、VMと外部の間でIP疎通を取りたい。 現時点では virtio-net デバイスをゲストカーネルのネットワークインターフェイスとして認識させることができたので、 ひとまずそこまでのログを残しておく。 やったことを大きく分けると、(1) ゲストのLinuxカーネルに対してvirtio-netデバイスをPCIデバイスとして認識させ、 (2) virtio-netデバイス初期化を完了させることでネットワークインターフェイスとして登録させることの2点。 virt queue上の操作やパケットのやり取りについては、この記事には含まれない。 例によって、コミット単位で実装の経過を残しておく。 fc02176d lspciコマンドの追加 busyboxにはlspciコマンドが同梱されているが、pci.ids ファイル 4 が存在しない。 pci.ids はベンダIDやデバイスIDなどの数値と、それに対応する文字列が組になっているようなファイルである。 このファイルがあれば、人間に読みやすいフォーマットで出力できる。 後々のデバッグをスムーズに進めたいので、対応させておいた。 e126392e PCI Config空間に対するIOエミュレーション カーネルがPCIデバイスを認識するための重要なフェーズ。 PCI Config 空間を読む方法はいくつかあるようだが、ここではタイプ1 5 と呼ばれる方法でアクセスした。 ここで使われるIOポートのアドレスは以下の通り。 0xcf8:アドレスレジスタに対応する。バス番号、デバイス番号、Function番号、PCI Config 空間内のオフセットに対応する。 0xcfc ~ 0xcff:データに対応する。 アドレスレジスタは32bit幅で以下のように解釈される。 位置 内容 Bit 31 Enable Bit Bit 30-24 Reserved Bit 23-16 Bus Number Bit 15-11 Device Number Bit 10-8 Function Number Bit 7-0 Register Offset ざっくり PCI Config 空間のあるオフセットにあるデータを読みたいときは次のような手続きになる。...

January 24, 2022

KVMを使った自作VMMのSMP対応

はじめに gokvm開発 1 2 の近況報告。 これまでは1つの仮想CPUにしか対応していなかった。 マルチCPUのためSMP(Symmetric Multiprocessing)対応させたいと思い立ってから2~3週間くらい試行錯誤し、無事実装することができた。 自分の知る限り KVM でVMMを作ってみたという取り組みを探す中で、 具体的にSMP対応とはどのような実装なのか解説されている資料がなかなか見つからなかった。 稚拙な記事ではあるけれど、今後自作VMMに挑戦する方にこの記事が役に立てば嬉しい。 例によってコミット単位に開発の経過を紹介していく。もちろん実際にはもっともっと泥臭い実装から始めていて、 何度もgit rebaseを繰り返しながら、最終的に解説できるよう粒度を調整したので、コミットのタイムスタンプはあてに出来ない。 #34 vCPUスレッドを複数生成 プルリクエストをいただいた。まずはioctl(fd,KVM_CREATE_VCPU,...)でvCPUを複数生成できるよう変更する。 その後vCPUごとに個別のスレッドを生成して各vCPUごとに独立してioctl(fd,KVM_RUN,...)を発行する。 vCPUはライフタイム全体を通して、同一のスレッドからioctlを発行する必要がある。 Go言語の場合にはスレッドの代わりにgoroutineを使うことが多いので、 runtime.LockOSThread()を呼び出してgoroutineとスレッドを静的に関連づけた。 ce22a91 struct mpf_intel の実装 vCPUがカーネルに認識されるためにはIntel MultiProcessor Specification 3 に従ったデータ構造を認識させる必要がある。 このデータ構造はLinuxカーネルの中で struct mpf_intel のPhysPtrが指す先 struct mpc_table に対応する。 コード 4 を読むと、チェックサム・バーション・マジックナンバーを読み取ることができたので、 仕様書は斜め読みしかしていない。 このデータ構造はどこに配置すれば良いのか。仕様書を読むとExtended BIOS Data Area (EBDA)の最初の1KB以内とあるのでそこに配置することにした。 EBDAは典型的に 0x0009FC00 に置かれる 5 ようなので、それに倣った。 a. In the first kilobyte of Extended BIOS Data Area (EBDA), or b. Within the last kilobyte of system base memory (e....

November 25, 2021

Understanding Linux Network Internal 1~2部 読書メモ

このブログではネットワークに関する比較的新しい技術について触れてきたが、たまには古きを温めるのも良いだろうということで読んでみた。Linuxカーネルは今後も長きにわたって使われるはずで、カンペキな理解でなくとも、取っ掛かりだけでも掴んでいる意味は大きいと思う。この本は1,000頁超えで、1~7部から構成されているので、一気に読むのはモチベーション維持が難しいと思う。この記事ではとりあえず現時点で読んだところまでをまとめたい。カーネルバージョン 2.6.39 のソースコードを手元に置いて、読み進めていった。ビルド方法などは前回の記事 1 のとおり。 1部 ネットワークに関する重要なデータ構造として struct sk_buff と struct net_device がある。まずはこの2つのデータ構造を掴むことが肝要だと思う。struct sk_buff は(フラグメンテーション云々の話を抜きにすると)1つのパケットに対応する。しばしばそのインスタンスは skb という名前が付けられる。skb->data が処理を担当しているネットワークレイヤのヘッダを指している。例えば、L2の処理を行っている際にはskb->data はL2ヘッダ の先頭を指している。処理の進行に伴って、このポインタは移動していく。実データの前後に余白が設けられている。 +------------+ skb->mac skb->nh | | | | | head-----------> +------------+ | | | | | headroom | v v | data-----------> +------------+ +---------+---------+---------+--- | | | | | L2 | L3 | L4 | | tail | | Data | | header | header | header | ... | | | | | +---------+---------+---------+--- | end | | | | ^ ^ | | +----------> +------------+ | | | | | | tailroom | | | | +-------------> +------------+ +---------+ | | skb->data +------------+ struct sk_buff この構造体にどんなメンバがいるか見ていく。users が参照カウンタに対応していて、sk_getやkfree_skbで操作できる。mac_header など各レイヤに対応するポインタもある。cbはコントロールバッファの略で、48バイトの領域を各レイヤの中でプライベート(他のレイヤを意識せず)に使える。struct sk_buffは双方向リストで管理されていて、リスト全体は struct sk_buff_head に対応する。デバッガを使って、中身を見ていく。送信を担当する関数にアタッチしてみると、struct sk_buff内部に保持されたIPヘッダ の中身を見ることができる。...

August 26, 2021

BusyboxベースのミニマルなLinux環境を作りQEMUで起動

すでに多くの方が似たような取り組みを行っていてブログ記事 1 2 3 として丁寧にまとめられているように、 やはりこういった環境を手元にさっと作れることの意味は大きいと思う。 ここではざっくりとした仕組みを記録しておく。 成果物をスクリプトとしてまとめGithubにあげている。 特徴 CentOS6、CentOS7、Ubuntu20.04などメジャーなディストリビューション向けカーネルのビルドに対応しているので、実務よりの応用ができる Busyboxを使ってユーザランドをメモリ上に展開するので、起動のたびにピュアでミニマルな環境を作れる SSHログインや外部ネットワーク疎通が可能なので、他システムとの連携が絡む動作を検証しやすい GDBを使ったデバッグによってカーネル内部のデータを参照できる 現時点ではx86/64のみに対応している カーネルのビルド カーネルのビルドは端的に言えば、ビルド設定を.config ファイルに記述し、makeコマンドを叩くことに対応する。.config はテキストファイルなので適用なエディタでも編集できるが、専用のコマンド(make oldconfig、make defconfig、make menuconfig など)が用意されているので、それを使うことが多い。カーネルはアップストリームのものと各ディストリビューションが手を加えたものがあるがここでは以下の全てのカーネルをビルドできるよう環境を整えた。 upstream (kernel v2.6.39) centos6 (kernel v2.6.32-754.35.1.el6) centos7 (kernel v3.10.0-1160.13.1.el7) ubuntu20.04 (kernel v5.4.0-65.73) 最近のGCCで古いカーネルをコンパイルするのは難儀なので、カーネルバージョンごとにビルド専用Dockerイメージを用意した。例えばカーネルv2.6.39はCentOS6のビルド環境を使ってビルドすることにした。 FROM ghcr.io/buddying-inc/centos68:latest RUN sed -i "s|#baseurl=|baseurl=|g" /etc/yum.repos.d/CentOS-Base.repo \ && sed -i "s|mirrorlist=|#mirrorlist=|g" /etc/yum.repos.d/CentOS-Base.repo \ && sed -i "s|http://mirror\.centos\.org/centos/\$releasever|https://vault\.centos\.org/6.10|g" /etc/yum.repos.d/CentOS-Base.repo RUN yum install -y gcc perl glibc-static kernel kernel-devel \ autoconf zlib-devel zlib-static openssl-static openssl-devel 上のDockerfileをもとに buildenv-v2....

July 7, 2021

MininetでFRR(BGP Unnumbered)を動かす

https://github.com/bobuhiro11/mininetlab の紹介。 Mininet を使うと単一のマシン上でいくつかのスイッチとホストを動作させることができる。 これを使って仮想的にホストを2つ立ち上げ、その間をFRRパッケージに含まれるBGP(Unnumbered)で接続してみる。 Mininetでは以下のようにPythonでトポロジや各ホストにおけるコマンド実行について記述できる。 一見複雑にみえるFRRもdaemons、vtysh.conf、frr.confの3つのファイルを正しく配置しておけば、 frrinit.sh startで簡単に起動できる。 ホストごとにnetnsは分割されているが、mountns は分割されていない(要確認)ため、 /etc/frrや/var/run/frrが2つのホスト間で衝突してしまいFRRが正常に起動できなかった。 これはprivateDirs = ['/etc/frr', '/var/run/frr'] によって回避できる。 #!/usr/bin/env python from mininet.net import Mininet from mininet.log import setLogLevel import time frr_conf = ''' hostname {name} password zebra ! router bgp {asnum} bgp router-id {router_id} bgp bestpath as-path multipath-relax neighbor h1-eth0 interface remote-as external neighbor h2-eth0 interface remote-as external address-family ipv4 unicast network {router_id}/32 network {network} exit-address-family ! line vty ! end ''' vtysh_conf = ''' service integrated-vtysh-config ''' daemons = ''' bgpd=yes vtysh_enable=yes zebra_options=" -A 127....

May 8, 2021

KVMを使ったVMMを自作してLinuxを起動するまでの記録 2

2021/2/24 WSL2 サポート 4f6b785 WSL2(Windows Subsystem for Linux 2)のUbuntu 20.04で gokvm を実行すると、 IOポート 0x64 への出力が無限に繰り返され、Initプロセスの起動まで到達しなかった。 どうやら PS/2 キーボード周りの挙動が原因のようだ。 kvmtool では in (0x61) に対して 0x20 を返している 1 のでそれを踏襲する形で対応した。 IOポート 0x61は NMI (Non-Maskable Interrupt)のステータスとコントロールレジスタとして使われているようだ 2。 このステータスレジスタの内容を調べると、bit 5はmirrors timer 2 output condition を意味するが、 これ以上は解釈できず。 理解できていない部分はあるが、結果として WSL2 での ゲストVMの起動もできるようになった。 0061 r KB controller port B control register (ISA, EISA) system control port for compatibility with 8255 bit 7 parity check occurred bit 6 channel check occurred bit 5 mirrors timer 2 output condition bit 4 toggles with each refresh request bit 3 channel check status bit 2 parity check status bit 1 speaker data status bit 0 timer 2 gate to speaker status 出典:XT, AT and PS/2 I/O port addresses...

March 3, 2021

KVMを使ったVMMを自作してLinuxを起動するまでの記録

はじめに KVMを利用したナイーブで実験的なVMMを作ってみた。 ioctl で /dev/kvm を叩いて仮想マシンを作成し、その上でLinux Kernelとユーザプロセスを起動できる。 Kernelのデバイスドライバから認識できる程度の非常に簡素なシリアルコンソールのエミュレーションも実装したので、 ログインシェルから操作ができる。 一方で、ネットワーキングやディスクについては現時点ではまだサポートしていない。 最近はKVMを従来のような仮想マシンとしての使い方だけでなく、 マルチテナントなクラウド環境において分離レベルを強化するために、 Google gVisor 1 や Kata Containers 2、 Amazon Firecracker 3 をはじめとした コンテナやマイクロVMでの使い方が登場してきた。 今回作ったgokvmは標準ライブラリのみを使いGo言語で実装したもので、 全体で1,500行程度(ブログ記事作成時点)なので、 自分と同じようにKVMやLinuxのブートプロセスに興味のある方にはとっかかりとして役立つかなと思う。 コミットログを見ながら、何をどう実装したのかについて振り返ってみる。 2021/1/30 プロジェクト始動 632c6e0 最初のコミット。README.md、.gitignore、LICENSEファイルを配置しただけで、 特に特筆することはない。似たようなプロジェクト 4 5 や LWN.net の記事 6 を調べていた。 ミニマムな実装で Linux ユーザランドまでブートさせるようなものは見当たらなかった。 ざっと調べただけなので調査漏れがあるかも。 もともとはkvmtool 4 がその立ち位置だったのかもしれないが、 ちょっとコードが巨大に感じた。kvm-host.c 5 は250行程度のCのコードでkernelのブートができるが、 ユーザランドまでは到達できていないようだ。 2021/2/4 bzImage・initrdのビルドとKVMのラッパー実装 69e3ebb 動作確認用のbzImageとinitrdを make コマンドから生成できるようにした。 bzImageはLinux Kernel本体、initrd はメモリ上の一時的なファイルシステムに対応する。 Linux Kernel バージョンはプロジェクト開始時点で最新の 5.10 を使った。 make tinyconfig を実施したのち、make menuconfig を使って追加で必要なconfigを有効にした。 initrdは、Busyboxをベースとした。 Linux KernelとBusyboxの ....

February 18, 2021

SRv6のLinux Kernel実装

SRv6とは SRv6はIPv6拡張の一つでSource Routingを実現するもの。Source Routingは、データ送信者がその宛先だけでなく、経路についても指定することを意味する。 経由するノードをSID(Segment Identifier)によって識別し、そのリストをパケットヘッダに含めることで、経路を自由に制御できる。SRv6では、IPv6アドレスがSIDに対応する。 SRv6は、EITF(Internet Engineering Task Force)を中心に仕様の策定が進められている 1 。 2020年3月にはRFC8754 2 として公開された。 SRv6で使われるIPv6ヘッダのSRH(Segment Routing Header)について詳しく見ていく。 まず、Routing TypeはSegment Routingではマジックナンバー4になる。 Segment List[0] ~ Segment list[n] のエントリに、最後のセグメントから降順に経由させたいセグメント一覧を列挙していく。 次のセグメントへの番号をSegments Left、最後のSegmentの番号をLast Entryに格納する。 セキュリティ機構であるHMACなど付加情報がある場合には、TLV(Type Length Value)として追加する。 Routing headers are defined in [RFC8200]. The Segment Routing Header (SRH) has a new Routing Type (4). The SRH is defined as follows: 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Next Header | Hdr Ext Len | Routing Type | Segments Left | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Last Entry | Flags | Tag | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | | Segment List[0] (128-bit IPv6 address) | | | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | | | ....

January 17, 2021

QEMU/KVM on WSL2 のログ

Windows10 WSL2上のゲストから/dev/kvmを経由して、その上にさらにネストさせる形で仮想マシンを動かすことができた。 環境 Windows 10 Pro Insider Program (Devチャネル、OSビルド 20246.1) 1 WSL2上のゲスト Ubuntu 20.04.1 LTS (Focal Fossa) Linux 4.19.128-microsoft-standard カーネルパラメータ initrd=\initrd.img panic=-1 nr_cpus=4 swiotlb=force pty.legacy_count=0 QEMU emulator version 4.2.1 (Debian 1:4.2-3ubuntu6.7) Intel(R) Core(TM) i7-5500U CPU @ 2.40GHz 手順 WSL2(Windows Subsystem for Linux 2)全体の設定 C:\Users\ユーザ名\.wslconfig に以下の設定を追加することで、 仮想化支援のネストを許可する。 [wsl2] nestedVirtualization=true 参考にしたブログ記事 2 では、WSL2上のゲストカーネルを再ビルドするような手順が紹介されているが、今回は必要なかった。 単純に nestedVirtualization=true とするだけで対応できた。 動作確認 Part.1 QEMU/KVM上でcirrosイメージを動かしてみる。 # non-root ユーザからも KVM の利用を許可 $ sudo chmod a+rw /dev/kvm $ kvm-ok INFO: /dev/kvm exists KVM acceleration can be used # cloud-init を無効化した cirros イメージを取得 $ wget https://github....

November 4, 2020

vDPA(Virtio Data Path Acceleration)メモ

仮想マシンやコンテナ環境で、高パフォーマンス(NICワイヤレート)かつ柔軟なIOを実現する方法。 まだあんまり日本語の情報は見つからない。 触ったわけではないので、勘違いなどあるかも。 vDPAカーネルフレームワーク 2020年3月に、vDPA カーネルフレームワークがLinux 5.7にマージされた。 vDPAカーネルフレームワークが扱うvDPA デバイスとは、 データプレーンがvirtio仕様、コントロールプレーンがベンダ仕様であるデバイスを指す。 ゲスト上のvirtio-netデバイスから見ると、データプレーンがホストをバイパスして物理NICに直接アクセスし、 コントロールプレーンがホストのvDPAフレームワーク(とベンダ依存のドライバ)を経由する、と捉えることができる。 かつてvDPAカーネルフレームワークは、mdevベースで作られた。 mdevはVFIOパススルー時にコントロールプレーンを仲介するもので、 ベンダ依存のコマンドとエミュレートされたPCIデバイス間の変換を担当する。 ただ以下の理由でmdevベースのアプローチを辞め、VFIOから独立した新しいサブシステムとしてvDPAカーネルフレームワークを設計した。 VFIOとmdevは、vDPAと比べるとレイヤの低い部分で抽象化を行っているため、レイヤの高いAPIをVFIOに取り入れるのは自然ではない 世の中のNICがすべてVFIO IOMMUの設計に適しているわけではない vDPAカーネルフレームワークはvhostキャラクタデバイスを提供するので、 QEMUのようなユーザスペースのドライバからvhostデバイスとして扱える。 独立したサブシステムなので、新たなハードウェアの機能に追従できる。 例えば、ライブマイグレーションのために、vhost API経由でデバイスの状態を保存・復元することができる。 また、SVA(Shared Virtual Address)やPASID(Process Address Space ID)もサポートする。 QEMUのようなユーザスペースでホスト・ゲストを仲介するレイヤが必要になるが、 SR-IOVによって単純にパススルーする構成と比べると、攻撃の対象領域を小さく保てる。 Intel Scalable IOVやSub Function(SF)とも相性が良い。 vDPA DPDK フレームワーク vDPAのもう一つのフレームワークとして、ホストのDPDKを使った vDPA DPDK framework も存在する。 QEMUから見ると、単純に vhost-user バックエンドと接続するだけで良い。 ほとんどのメジャーなNICに対して、このvDPA DPDK driverが存在している。 ただvDPA DPDK フレームワークには以下の制約があった。 vhost-userはユーザスペースのAPIなので、ホストのカーネルサブシステムを操作することができない。例えば、eBPFのような機能と協調して動かすことができない。 DPDKはデータプレーンに注力しているため、ハードウェアの操作するためのツールを提供していない。 これらの制約を取り除くために、vDPAカーネルフレームワークが必要だった。 How deep does the vDPA rabbit hole go?, redhat.com にさらに突っ込んだ内容が書かれている。 既存のアプローチとの比較 vDPAと既存のアプローチとの比較をしてみる。 項目 vhost-net vhost-user virtio full HW offload vDPA パフォーマンス 低 中 高 高 NIC側のデータプレーンサポート No No Yes Yes NIC側のコントロールプレーンサポート No No Yes No ライブマイグレーション Yes Yes No Yes 成熟度 高 高 高 中? 出典:Achieving network wirespeed in an open standard manner: introducing vDPA, redhat....

October 27, 2020

XDPメモ(アーキテクチャ、性能、ユースケース)

はじめに The eXpress data path: fast programmable packet processing in the operating system kernel 1 を読んだ。 この文章はほとんどこの論文をもとに書いたが、一部ニュース記事を引用している。 eBPF/XDPが流行っているということは、BCC、bpftrace、Facebook Katran、Cloudflare Gatebot などeBPF/XDPを使うプロジェクトのGithub Star数から感じ取れる。 eBPF/XDPには、特殊なハードウェア・ソフトウェアに依存せず、 カーネルの仕掛けとして高速パケット処理を実現できるという強力なメリットがある。 一方であまり弱点を主張するような記事は見当たらないので、実際のところどうなのか感触を知りたい。 XDPを使うとNICデバイスドライバのコンテキストで、eBPF Verifilerの制約はありつつも、 比較的自由にパケット処理を実現できる。 また、成熟したLinuxのネットワークスタックと共存しつつ、1コアで24Mppsという高速なパケット処理を実現できる。 XDPプログラムは、eBPFの制約のもとC言語で記述することができ、clangでELFバイナリにコンパイルする。 XDPの競合としてカーネルバイパスなDPDKがある。両者の特徴は以下のとおり。 DPDK: カーネルバイパスによってコンテキストスイッチを避け高速化を図る コアを専有する(特定のコアでCPU 100%に張り付かせてポーリング) スループットとレイテンシどちらの観点で見てもDPDKのほうが優れている ネットワークスタックを再実装する必要がある XDP: 専用コアが不要(電力面などで有利) 容易にロード・アンロードできる 名前空間などカーネルの機能に強く依存するコンテナ環境の普及で、XDPの重要度が増しているように感じる やはりカーネルネットワークスタックと共存できるというメリットが強い アーキテクチャ パケット着信のたびに、デバイスドライバのフックポイントでXDPプログラムが実行される。 このフックポイントは、デバイスドライバの処理の中でも初期に位置する(sk_buff の割り当て前)。 XDPプログラムは、ヘッダのパース、eBPFマップの読み書き、ヘルパ関数(FIBのルックアップなど)の呼び出しを経て、 最終的にパケットをどこに送り出すか決定する。 送り先については、XDPプログラムの終了コードで制御することができ、 ドロップさせる、同インターフェイスに送り返す、他インターフェイスに転送する、AF_XDPとしてユーザプログラムに送る、 通常のネットワークスタックに送る、から選択する。 2つ以上のXDPプログラムを同インターフェイスに紐付けたい場合には tail call として処理を引き渡すことができる。 外部のシステムとデータのやり取りをするために、eBPF Mapが用意されている。 eBPF Verifilerが厳しくチェックしているので、必ずeBPF Mapを使うことになりそうだ。 出典:The eXpress data path: fast programmable packet processing in the operating system kernel...

September 17, 2020

KVM TLB Shootdown Preemption メモ

KVM FORUM 2018 で発表されたTowards a more Scalable KVM Hypervisorについてのメモ。 [PATCH v8 0/4] KVM: X86: Add Paravirt TLB Shootdown ゲストOSはOSレベルの同期機構であるTLB shoot downやRCUの処理において、ホストOSのスケジューラの影響を受ける。ベアメタル環境であればすぐに完了する操作であっても、仮想環境上ではその遅延を無視することができなくなる。 TLB(Translatoin Lookaside buffer)は仮想メモリアドレスと物理メモリアドレスのマッピングをキャッシュするために使われる。あるCPUがそのマッピングを切り替えたとき、その以外のCPUでTLBをflushしなかればならない。これを TLB shoot down と呼ぶ。 モダンなOSでは、TLB shoot down はパフォーマンスクリティカルな箇所として位置づけされ、この処理によって遅延が発生しないようチューニングされている。TLBのflushはすぐさま完了するという前提で、IPI(Inter Processor Interrupt)で実装されている。Remote CPUのTLB flushの完了は busy wait で待っているため、ベアメタル環境とは相性が良い。一方で、仮想環境ではvCPUが別のゲストに横取りされたり、実行がブロックされたり、ということが起きるため、長時間にわたって busy wait が継続することになる。 この問題は、準仮想化TLB shoot down によって解決できる。準仮想化TLB shoot down では、動作していないvCPUに対する操作を遅延させておいて、次回そのvCPUが起動するときに、KVMがTLB flushを担当する。特にオーバコミットされた環境で顕著なパフォーマンス向上が見られる。 ゲストとホストどちらからも参照できるメモリ領域に「あるvCPUが preempt されたかどうか」を示すフラグを用意しておく。pv_mmu_ops.flush_tlb_others関数は、アクティブなvCPUに対してはIPI経由でTLB Flushの通知を送り、そうでないvCPUに対してはKVM_VCPU_FLUSH_TLBフラグを付与しておく。その後、KVMはKVM_VCPU_FLUSH_TLBフラグがついているvCPUを起動する際に、INVVPIDを発行する。VM数が増えるに従ってこのチューニングの効果が顕著に現れる。

July 1, 2020