FreeBSDで体験する次世代ネットワークスタック

[FreeBSD Advent Calendar]
(https://qiita.com/advent-calendar/2018/freebsd) 17日目です。

 ソケット API を前提としたネットワークスタックの設計は、20 年以上昔のコンピュータやネットワークの速度を前提に設計されています。現在では CPU はマルチコア化がすすみ、ネットワークは 10Gbps や 40Gbps といった具合に数桁高速になり、またストレージもディスクから、数桁高速でアクセス方法すら違う不揮発性メモリになりつつあるなど、大きな変化が起こっています。OS のカーネルは新たなデバイスドライバのサポートに加え、こういったハードウェアの高速化、あるいはそれらの関係の変化に対処するために新たな I/O サブシステムやAPIを実装したりと、日々進化を遂げています。

こうした新たなカーネルの仕組みを試すのにはいつも大掛かりなハードウェアのセットアップが必要なわけではなく、ものによっては VM を利用して簡単に試すことができます。この記事では、既存の http ベンチマークツールと簡易 web サーバを使って、まだ本家にはマージされてませんが、[PASTE](https://micchie.github.io/paste/) という高速ネットワークスタックを体感してみたいと思います。ホストは Mac か Linux, 仮想化ソフトウェアには [Virtualbox](https://www.virtualbox.org/wiki/Downloads), 仮想化ソフトウェアの管理には [Vagrant](https://www.vagrantup.com/) を使います。

まずは VM を作りましょう。freebsd という適当な作業ディレクトリを作って、その中に以下のような内容の Vagrantfile を作ります。

```ruby:Vagrantfile
Vagrant.configure("2") do |config|
  config.vm.network :forwarded_port, guest: 22, host: 3332, id: "ssh"
  config.vm.guest = :freebsd
  config.vm.synced_folder ".", "/vagrant", id: "vagrant-root", disabled: true
  config.vm.box = "freebsd/FreeBSD-13.0-CURRENT"
  config.vm.base_mac = "080027D14C26"
  config.ssh.shell = "sh"
#  config.vm.network :"private_network", ip: "192.168.18.18"
  config.vm.provider :virtualbox do |vb|
    vb.gui = true
    vb.customize ["modifyvm", :id, "--memory", "2048"]
    vb.customize ["modifyvm", :id, "--cpus", "2"]
    vb.customize ["modifyvm", :id, "--hwvirtex", "on"]
    vb.customize ["modifyvm", :id, "--audio", "none"]
    vb.customize ["modifyvm", :id, "--uart1", "0x3F8", "4"]
    vb.customize ["modifyvm", :id, "--uartmode1", "server", "/tmp/freebsd-ttyS1.sock"]
    vb.customize ["modifyvm", :id, "--nictype1", "virtio"]
#    vb.customize ["modifyvm", :id, "--nictype2", "82545em"]
#    vb.customize ["modifyvm", :id, "--nicpromisc2", "allow-all"]
  end

  config.vm.provision :shell, :inline => "[ ! -f /usr/local/bin/python ] && /usr/sbin/pkg install -y python || true"
  config.vm.provision :shell, :inline => "ln -sf /usr/local/bin/python /usr/bin/python || true"
  end
end
```
CPUコアの数やメモリの量は環境に応じて変えてください。メモリは最低 1GB 割り当てるとよいと思います。

同じディレクトリで、`vagrant up` すると、VM が起動します (ネットワーク越しにセットアップするので少し時間がかかります)。起動したら `/etc/rc.conf` の中にある、`ifconfig_DEFAULT="SYNCDHCP"` の行を、`ifconfig_em0="SYNCDHCP"` に変更します。そしたら今度は、`Vagrantfile` の中の三箇所のコメントアウト (# で始まる行) を外します。そして再度 `vagrant reload` すると、VM の em0 に 192.168.18.18 のアドレスが付いていて、ホスト側から ping が届くはずです。また、`vagrant ssh-config --host va0` で出力される内容をホスト側の .ssh/config に追記すると、その VM に `ssh va0` でログインできるようになります。

ここまでできたら、カーネル再構築の準備です。まずはユーザ権限でカーネルがコンパイルできるように以下のようにします。

```
sudo chown vagrant /usr/src
sudo mkdir /usr/obj
sudo chown vagrant /usr/obj
```
次に [https://github.com/freebsd/freebsd](https://github.com/freebsd/freebsd) からとってきた最新の FreeBSD CURRENT のコードを展開しましょう。展開後は、以下のようなディレクトリレイアウトになってると思います。

```
vagrant@freebsd:~ % ls /usr/src/
COPYRIGHT		cddl			secure
LOCKS			contrib			share
MAINTAINERS		crypto			stand
Makefile		etc			sys
Makefile.inc1		gnu			targets
Makefile.libcompat	include			tests
Makefile.sys.inc	kerberos5		tools
ObsoleteFiles.inc	lib			usr.bin
README			libexec			usr.sbin
README.md		release			xen
UPDATING		rescue
bin			sbin
vagrant@freebsd:~ % 
```

次に、[PASTE](https://micchie.github.io/paste/) という netmap を拡張して作られた高速ネットワークスタックのパッチを当てます。VM 内で、

```
git clone git@github.com:micchie/netmap.git
cp netmap/sys/net/* /usr/src/sys/net/
cp netmap/sys/dev/netmap/netmap* /usr/src/sys/dev/netmap/
echo dev/netmap/netmap_bdg.c optional netmap >> /usr/src/sys/conf/files
echo dev/netmap/netmap_stack.c optional netmap >> /usr/src/sys/conf/files
```
とすれば OK です。
Vagrant での　VM 起動時に最新のコードのスナップショットで起動していますので、普通に build kernel できるはずです。

```
cd /usr/src
make buildkernel KERNCONF=GENERIC=NODEBUG # 複数コアあれば -j3 のようにして複数スレッド使いましょう
sudo make installkernel KERNCONF=GENERIC-NODEBUG
```

新しいカーネルを有効にするために一旦 VM を再起動して、まず NIC を設定します。
```
sudo ifconfig em0 up
sudo ifconfig em0 -lro -tso -txcsum -rxcsum
```

簡易 web サーバ (phttpd) のディレクトリに移動してコンパイルします。

```
cd netmap/apps/phttpd
make clean; make
```

まずは phttpd を普通のソケットAPIを使って動かしてみます。中身は、`kqueue` ベースのイベントループが動いていて、`read()`/`write()` でネットワークデータを読み書きしています。

```
./phttpd
```
ホスト側から、`wrk` を使って、100 本のコネクションを使って HTTP リクエストを送ってみます。

```
michio@client:~ >wrk -d 3 -c 100 -t 100 http://192.168.18.18:60000 
Running 3s test @ http://192.168.18.18:60000
  100 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     6.77ms    1.30ms  23.04ms   90.36%
    Req/Sec   148.17     17.83   282.00     89.99%
  45375 requests in 3.11s, 6.53MB read
Requests/sec:  14612.95
Transfer/sec:      2.10MB
```

サーバ側は、`Ctrl+C｀で終了します。
次に、PASTE を有効にした簡易 web サーバを動かしてみます。

```
./phttpd -i em0
```
ホスト側から同じコマンドで HTTP トラフィックを生成します。

```
michio@client:~ >wrk -d 3 -c 100 -t 100 http://192.168.18.18:60000  
Running 3s test @ http://192.168.18.18:60000
  100 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.37ms  640.18us   9.06ms   92.63%
    Req/Sec   762.45     67.41     0.92k    71.13%
  235389 requests in 3.10s, 33.90MB read
  Socket errors: connect 0, read 25, write 0, timeout 0
Requests/sec:  75867.18
Transfer/sec:     10.93MB
```
かなり高速になりますね。

VM なのであくまで目安ですが、カーネルの技術だけでこんなにネットワークサーバの性能が変わるのかということを体感していただければと思います。
FreeBSD には netmap や vale をはじめ、TCP スタックを部分的に変更できるようにしてある TCP Function Block という仕組みや、最近では eBPF や不揮発性メモリドライバがマージされたりと、ネットワークスタックの研究に向いている機能がたくさん入っているので、ネットワークの研究に興味がある人とかもぜひ見てみると面白いと思います。