VirtualBox intnet と NIC と promisc の話

一般的な使い方では、自分の IP アドレスは MAC アドレス 1 つと対応している。Ethernet の普通の使い方では、そのアドレスについてだけ効率よく情報処理してれば、それでよい。

ところが色々な理由により、自分自身に初期設定される MAC アドレス以外のアドレスも扱えたほうが便利なケースが出てくる。すると先ほどの「MAC アドレス 1 つだけ」という限定を解除できると便利だし、できるようになっている。これは promiscuous mode と呼ばれる。

最近の NIC は、これまた様々な理由により、結果的に MAC アドレスは起動時に読みだして設定して使っているものが多い。この手の NIC はドライバの機能で、後から OS 側から別の MAC アドレスを設定できるようになっていたりする。大昔の NIC には、設定できないものもあったらしい。

新しい MAC アドレスで通信できるようにしたい、という要求には、どうやら情報がごっちゃになって理解されて、promiscuous mode でなければならないと思われている場合がある様子。確かに promiscuous にすればパケット送受信はできるけれど、普通の今どきの NIC なら MAC アドレスを再設定するだけで大丈夫。

# Vagrant / VirtualBox

ネットワークまわりの構成デモに Vagrant を使うケースが結構ある。例えばこんな感じに。

```
Vagrant.configure("2") do |config|
  config.vm.box = "minimal/xenial64"
  config.vm.synced_folder ".", "/vagrant", disabled:true

  config.vm.define :box1 do |node|
    node.vm.network "private_network", ip: "192.168.5.1", virtualbox__intnet: "A"
    node.vm.provider :virtualbox do |vb|
      vb.customize ["modifyvm", :id, "--nicpromisc2", "allow-all"]
    end
  end

  config.vm.define :box2 do |node|
    node.vm.network "private_network", ip: "192.168.5.2", virtualbox__intnet: "A"
    node.vm.provider :virtualbox do |vb|
      vb.customize ["modifyvm", :id, "--nicpromisc2", "allow-all"]
    end
  end
end
```

vagrant 用語の `virtualbox__intnet` は、virtualbox の internal network に[対応している](https://www.vagrantup.com/docs/virtualbox/networking.html)。この internal network が予想の斜め上に機能がついているので、注意が必要。独自の機能として、「未知の MAC アドレスは通さない」という制御機能がデフォルトで有効。これは上記で設定している `nicpromisc` で制御できて、`allow-all` にすれば、一般的な Ethernet switch のように任意の MAC アドレスが通るようになる。

何も指定しなければ `e1000` 互換仕様の仮想 NIC が準備される。これは MAC アドレスを再設定できる。ところが MAC アドレスを再設定すると、ineternal network はパケットを遮断する。理由は後述。これは一般的な動作とは異なるので大変混乱する。

さらに続けて NIC を promiscuous mode にすると、新しい MAC アドレスで通信できるようになる。ここで 2 回目の混乱をする。

# 原因

virtualbox の internal network は、仮想 NIC のアドレスが一致しているかどうか、あるいは promiscuous mode になっているかどうか、を判断基準にしてパケットを通すかどうかを決めている。ここまではいい。

困ったことに guest OS 側で NIC の MAC アドレスを再設定しても hypervisor 側にそれが伝わらないようになっている。ものすごく具体的に言うと、virtualbox のソースコードで `PDMINETWORKUP` に MAC アドレスの変更を伝えるインターフェースが無い。

`ip link set eth0 promisc on` は `promisc` を操作するけれども、これは `pfnSetPromiscuousMode` を通じて `virtualbox__intnet` の `IntNetR0IfSetPromiscuousMode` や `IntNetR0IfSetPromiscuousModeReq` に伝わる。

`ip link set eth0 addr 00:11:22:33:44:55` で同様に `virtualbox__intnet` の `IntNetR0IfSetMacAddress` や `IntNetR0IfSetMacAddressReq` に伝わればよかった。これが無い。

`virtualbox__intnet` 自体は、本来は MAC アドレス更新が通知されれば、正しく動作するように作られているようだ。

promiscuous mode を設定すると通信できるようになるのは、こちらは guest OS の設定が internal network に反映されるから、ということのようだ。

MAC アドレスの変更が伝わらないのは、おそらく意図的なもののように思える。伝わるように変更するにしても、そこそこ大きなパッチを書かないといけない見通しなので、いったん見送る。

# VirtualBox NIC

ちなみにデフォルトで選択される `e1000` である「Intel PRO/1000」系列や `virtio` である「virtio-net」は MAC アドレスを再設定できるが、「PCnet-PCI II」や「PCnet-FAST III」は MAC アドレスを変更しようとすると `EBUSY` を返された。

# vbox Patch

Doxygen が正しく生成されないことに気が付いてしまったので、仕方なく作成したパッチ。

```
diff --git a/Makefile.kmk b/Makefile.kmk
index 6cc3a3f73f..025c164293 100644
--- a/Makefile.kmk
+++ b/Makefile.kmk
@@ -460,7 +460,7 @@ VBOX_CORE_DOXYFILE_INPUT_DIRS = \
        src/VBox/Devices/Graphics \
        src/VBox/Devices/Graphics/BIOS \
        src/VBox/Devices/Input \
-       src/VBox/Devices/Networking \
+       src/VBox/Devices/Network \
        src/VBox/Devices/PC \
        src/VBox/Devices/PC/BIOS \
        src/VBox/Devices/Parallel \
```

# vbox build Vagrantfile

https://www.virtualbox.org/wiki/Linux%20build%20instructions から次のような Vagrantfile を作った。

```
Vagrant.configure("2") do |config|
  config.vm.box = "debian/jessie64"
  config.vm.synced_folder ".", "/vagrant", disabled:true
  config.vm.provider "virtualbox" do |vm|
    vm.memory = 2048
  end
  config.vm.provision "shell", inline: <<-SHELL
    dpkg --add-architecture i386
    apt-get update
    apt-get -y install gcc g++ bcc iasl xsltproc uuid-dev zlib1g-dev libidl-dev \
                libsdl1.2-dev libxcursor-dev libasound2-dev libstdc++5 \
                libpulse-dev libxml2-dev libxslt1-dev \
                libssl-dev doxygen yasm wine mscgen \
                python-dev libqt4-dev qt4-dev-tools libcap-dev \
                libxmu-dev mesa-common-dev libglu1-mesa-dev \
                linux-kernel-headers libcurl4-openssl-dev libpam0g-dev \
                libxrandr-dev libxinerama-dev libqt4-opengl-dev makeself \
                libdevmapper-dev default-jdk \
                texlive-latex-base \
                texlive-latex-extra texlive-latex-recommended \
                texlive-fonts-extra texlive-fonts-recommended
    apt-get -y install gcc-multilib g++-multilib
    apt-get -y install kbuild genisoimage libvpx-dev linux-headers-`uname -r`
    apt-get -y install git
    git clone https://github.com/mirror/vbox
    cd vbox
    ./configure --disable-hardening
    kmk docs
  SHELL
end
```