試験運用中なLinux備忘録・旧記事

はてなダイアリーで公開していた2007年5月-2015年3月の記事を保存しています。

VirtualBoxでブリッジ接続をする(Linuxホスト)

(2008/11/12)この記事の内容は古く、推奨できない方法についての記述も含んでいるため、「GNU/LinuxホストにおけるSun xVM VirtualBoxのブリッジ接続の設定を見直し」を参照。この記事への追記は今後行わない。
(2008/12/19)Sun xVM VirtualBoxバージョン2.1.0からブリッジ接続が簡単になっている。Sun xVM VirtualBoxの覚え書き(2008/12/19現在)」を参照。


TUN/TAPデバイスとブリッジを組み合わせた上で、適切な準備を行うことで、VirtualBoxでも、仮想マシンをホストOSと同じネットワークに参加させることができる。
VMware製品と比べると非常に面倒。
(2007/8/1)ゲストOSのIPアドレスDHCPが使えることが確認できたため、固定で指定する必要はないことが分かった。iptablesの設定でブロックされていただけだったので、詳細は追記を参照。

必要なもの

  • カーネルのTUN/TAPドライバとブリッジのモジュール
  • TUN/TAPのインターフェースを設定するtunctlコマンド。「usermode-utilities」パッケージに含まれる
  • bridge-utils(ブリッジの設定ツール)
  • sudo(管理者や別のユーザとしてコマンドを実行できる)

(2007/10/19)TUN/TAPインターフェースの設定には、VirtualBox付属のVBoxTunctlが使える。

ブリッジとTUN/TAPドライバをビルドするためのカーネル設定項目

どちらとも、組み込みにもできるのだが、モジュールとしてビルドしておく。ディストリ付属カーネルでは、両方ともモジュールとして存在している可能性が高い。

Networking  --->
 Networking options  --->
  <M> 802.1d Ethernet Bridging
Device Drivers  --->
 Network device support  --->
  <M> Universal TUN/TAP device driver support

TUN/TAPデバイスのアクセス権(udevの設定)

最終的に、一般ユーザから実行して読み書きができればよいのだが、

  • 「tuntap」などの名前のグループを作成して所有グループにして、モード0660
  • 既存の「vboxusers」などのグループの所有にして、モード0660
  • 所有グループはデフォルトにしておいて、モードは0666

など、色々なやり方が考えられる。手元の環境では「vboxusers」というグループがあり、既に一般ユーザをメンバーにしているので、これを所有グループにしておくことにする。
ファイル名: /etc/udev/rules.d/50-udev.rules など

# 1番目(多少面倒だが無難)
KERNEL=="tun", NAME="net/%k", GROUP="tuntap", MODE="0660"

# 2番目(TUN/TAPのためにグループを作るのが面倒)
KERNEL=="tun", NAME="net/%k", GROUP="vboxusers", MODE="0660"

# 3番目(非推奨)
KERNEL=="tun", NAME="net/%k", MODE="0666"

sudoで一部コマンドをパスワード入力なしで使用するための設定

仮想マシンの開始・終了時にスクリプトによりインターフェース制御を行う場合、その中で実行されるコマンドの中にroot権限が必要なものがあるため、sudoコマンドをパスワード入力無しで使用できるように設定する必要がある。
以下は、ここで使用されるコマンド群に対してのみ、wheelグループのメンバーがパスワード無しで使用できるような設定例。DHCPクライアントは、dhcpcd以外の場合、環境に合ったものに置き換える。固定でIPアドレスを割り当てている場合、DHCPクライアントの場所は書く必要がない。また、modprobeも、やり方によっては不要かも。
sudoの設定を行うには、root権限でvisudoを実行し、設定ファイルを編集する。

# Cmnd alias specification
Cmnd_Alias TUNTAP_BRIDGE=/sbin/modprobe, \
                         /sbin/ifconfig, \
                         /sbin/brctl, \
                         /opt/VirtualBox/VBoxTunctl, \
                         /sbin/route, \
                         /sbin/dhcpcd
(中略)
# User privilege specification
(中略)
%wheel	ALL=(ALL)	ALL
(中略)
%wheel ALL = NOPASSWD: TUNTAP_BRIDGE

(2007/10/19)VirtualBoxに付属しているVBoxTunctlを使用するよう修正

TUN/TAPとブリッジ接続を設定するスクリプト

スクリプトは3つに分けてみた。メッセージ表示処理はしていないし、ifconfigの出力からIPアドレスなどを取得する処理も、環境によってうまくいかない可能性があり、コマンドの場所なども、環境に応じた調整が必要かもしれない。

  1. VirtualBoxの起動前に準備として実行させ、終了後に後始末として実行させる
  2. VirtualBoxから受け取った名前のTUN/TAPのデバイスを作成、一般ユーザの所有とし、ブリッジのポートに追加(「設定アプリケーション」に指定する)
  3. VirtualBoxから受け取った名前のTUN/TAPのデバイスをブリッジから外し、インターフェースも削除する(「終了アプリケーション」に指定する)

(2007/8/1)最後のスクリプト「終了アプリケーション」に指定しなくても、該当する動作が自動的に行われるため、指定する必要がないことが分かった。

ファイル名: ~/bin/vbox-init-system

#! /bin/sh

USE_DHCP=0
ETH=eth0
BRCTL=/sbin/brctl
#BRCTL=/usr/sbin/brctl
DHCP_CLIENT=/sbin/dhcpcd

MODULES_LIST='tun bridge vboxdrv'
# MODULES_LIST='tun bridge kqemu'
# MODULES_LIST='tun bridge kvm-amd kvm'
# MODULES_LIST='tun bridge kvm-intel kvm'

DEFAULT_GW=$(/sbin/route | grep default | awk -F\  '{printf $2}')

start ()
{
  ip_address=$(/sbin/ifconfig ${ETH} | head -n 2 | tail -n 1 | cut -f 2 -d : | cut -f 1 -d \ )
  broadcast=$(/sbin/ifconfig ${ETH} | head -n 2 | tail -n 1 | cut -f 3 -d : | cut -f 1 -d \ )
  net_mask=$(/sbin/ifconfig ${ETH} | head -n 2 | tail -n 1 | cut -f 4 -d : | cut -f 1 -d \ )
  ## 必要なモジュールを読み込む
  for i in ${MODULES_LIST}; do sudo /sbin/modprobe -q $i; done
  ## ブリッジの作成
  sudo ${BRCTL} addbr br0 || exit 1
  ## ブリッジの設定
  sudo ${BRCTL} setfd br0 0 || exit 1
  sudo ${BRCTL} sethello br0 2 || exit 1
  sudo ${BRCTL} stp br0 off || exit 1
  ## NICのIPアドレス割り当てを解除
  sudo /sbin/ifconfig ${ETH} 0.0.0.0 || exit 1
  ## ブリッジにNICを追加
  sudo ${BRCTL} addif br0 ${ETH} || exit 1
  ## ブリッジにIPアドレスなどを設定して有効にする
  if [ ${USE_DHCP} = 0 ]; then
    sudo /sbin/ifconfig br0 ${ip_address} broadcast ${broadcast} netmask ${net_mask} up || exit 1
    sudo /sbin/route add default gw ${DEFAULT_GW} || exit 1
  else
    sudo ${DHCP_CLIENT} br0 || exit 1
  fi
}

stop ()
{
  ip_address=$(/sbin/ifconfig br0 | head -n 2 | tail -n 1 | cut -f 2 -d : | cut -f 1 -d \ )
  broadcast=$(/sbin/ifconfig br0 | head -n 2 | tail -n 1 | cut -f 3 -d : | cut -f 1 -d \ )
  net_mask=$(/sbin/ifconfig br0 | head -n 2 | tail -n 1 | cut -f 4 -d : | cut -f 1 -d \ )
  ## ブリッジを無効にする
  sudo /sbin/ifconfig br0 down || exit 1
  ## ブリッジからNICを削除
  sudo ${BRCTL} delif br0 ${ETH} || exit 1
  ## ブリッジを削除
  sudo ${BRCTL} delbr br0 || exit 1
  ## NICを再設定
  if [ ${USE_DHCP} = 0 ]; then
    sudo /sbin/ifconfig ${ETH} ${ip_address} broadcast ${broadcast} netmask ${net_mask} || exit 1
    sudo /sbin/route add default gw ${DEFAULT_GW} || exit 1
  else
    sudo ${DHCP_CLIENT} ${ETH} || exit 1
  fi
  ## モジュールを削除
  sudo /sbin/modprobe -qr ${MODULES_LIST}
}

case "$1" in
  start)
    start
    ;;
  stop)
    stop
    ;;
  *)
    echo "Usage: $(basename "$0") {start|stop}"
    exit 1
esac

(2007/7/17)上のスクリプトにおいて、コマンドの実行に失敗したら処理を停止するように修正・2度続けて実行しても安全に止まるようになった
(2007/8/1)読み込むカーネルモジュールを選択するように修正(QEMUでも使えるようにした)
(2008/10/19)sethelloの値が不正だったのを修正(必要がなければこの行は消してもOK)
(2008/10/24)brctlの場所を変数に入れる形に修正・もし「[ルータのホスト名]: 不明なホスト」のように出てしまう場合は/etc/hostsにルータのホスト名とIPアドレスを記述する

ファイル名: ~/bin/vbox-init-user

#! /bin/sh

## 自分の所有するTUN/TAPインターフェースを新規作成
sudo /opt/VirtualBox/VBoxTunctl -u $(whoami) -t $2
## インターフェースを有効にする
sudo /sbin/ifconfig $2 0.0.0.0 up
## ブリッジに追加
sudo /sbin/brctl addif br0 $2

(2007/10/19)VirtualBoxに付属のVBoxTunctlを使用するよう修正

ファイル名: ~/bin/vbox-init-user-stop (実は不要?)

#! /bin/sh

## TUN/TAPインターフェースをブリッジから削除
sudo /sbin/brctl delif br0 $2
## インターフェースを削除(所有者=ユーザの権限で実行させる)
/usr/bin/tunctl -d $2

「$2」には、指定した「tun0」などのインターフェース名が入る。仮想マシンを追加する場合、「tun1」など、別の名前にするとよい。
使い方は、

$ vbox-init-system start

で一連の準備を行った後でVirtualBoxを起動し、終了後に

$ vbox-init-system stop

で元に戻す。後始末はしなくても、ネットワークは使用できるが、dmesgを見ると、

device eth0 entered promiscuous mode

のままで、後始末を終えるまではずっと、NICがネットワーク上の他のマシン宛てのデータを捨てずに拾うモード(プロミスキャス/無差別モード)で動作するため、これが気になるのであれば、stopの処理は行うのがよい。

device eth0 left promiscuous mode

という行が出て、通常のモードに戻る。なお、VMware製品の場合、ブリッジ機能を使用すると、仮想マシンの起動/終了時に自動的にこのモードを切り替えているようだ。

下は、VirtualBox仮想マシンのネットワーク設定例。
(2007/8/1)終了アプリケーションの設定が無くても後始末ができることが分かったため、設定を修正

Gentoo Linuxで起動時にTUN/TAPとブリッジを設定するための設定

起動時にTUN/TAPデバイスを作成し、NICの接続とブリッジさせるようにするには、既存のeth0などの設定を消した上で
ファイル名: /etc/conf.d/net

config_eth0=( "null" )
tuntap_tun0="tap"
config_tun0=( "null" )
tunctl_tun0="-u [ユーザ名]"
# config_br0=( "dhcp" )
config_br0=( "[ブリッジのIPアドレス] netmask [ネットマスク]" )
routes_br0=( "default via [デフォルトゲートウェイ]" )
bridge_br0="eth0 tun0"
depend_br0 ()
{
  need net.eth0 net.tun0
}
brctl_br0=( "setfd 0" "sethello 0" )

のようにする。更に、OS起動時のスクリプト自動起動の設定として

$ cd /etc/init.d/
$ for i in br0 tun0; do sudo ln -s net.lo net.$i; done
$ sudo rc-update del net.eth0 default
$ sudo rc-update add net.br0 default

を行う。ただ、この方法を使うよりは、仮想マシン起動時にスクリプトを実行したほうがよい気がする。なお、この場合、VirtualBoxの設定で、スクリプトの欄は空欄にしておく。

TUN/TAPの初期化に失敗する問題(CAP_NET_ADMIN絡み)

準備をして仮想マシンを起動させようとしても、「VERR_HOSTIF_INIT_FAILED」というエラーが出て、TUN/TAPデバイスが使えない。

これだけではよく分からなかったのでQEMUでも試したところ

warning: could not configure /dev/net/tun: no virtual network emulation

となり、このメッセージをWeb検索してみた。

http://www.mail-archive.com/qemu-devel@nongnu.org/msg07032.html
で始まるスレッドなどによると、Linux 2.6.18以降、QEMUでTUN/TAPを使用する場合にも、rootでなければうまくいかない*1という状態が続いているという。手間のかからない回避策としては、カーネルのTUN/TAPドライバのソースを修正するか、アプリケーション側をsuidで動かすかのどちらか。
QEMUの場合はsetuidで動くのだが、VirtualBoxでは

$ sudo chmod u+s /opt/VirtualBox/VirtualBox
$ virtualbox
/opt/VirtualBox/VirtualBox: error while loading shared libraries: VBoxRT.so: cannot open shared object file: No such file or directory

セキュリティ上の理由か、共有ライブラリが読めず、失敗。
結局、カーネルdrivers/net/tun.cを修正することに。
要領としては、

!capable(CAP_NET_ADMIN)

の条件を全て外していけばOK。今後、数が増えたとしても、このやり方で対処できる。

--- linux-2.6.21-suspend2-r6/drivers/net/tun.c.orig
+++ linux-2.6.21-suspend2-r6/drivers/net/tun.c
@@ -461,7 +461,7 @@
 
 		/* Check permissions */
 		if (tun->owner != -1 &&
-		    current->euid != tun->owner && !capable(CAP_NET_ADMIN))
+		    current->euid != tun->owner)
 			return -EPERM;
 	}
 	else if (__dev_get_by_name(ifr->ifr_name))
@@ -472,9 +472,6 @@
 
 		err = -EINVAL;
 
-		if (!capable(CAP_NET_ADMIN))
-			return -EPERM;
-
 		/* Set dev type */
 		if (ifr->ifr_flags & IFF_TUN) {
 			/* TUN device */

(2007/10/13)Linux 2.6.23の時点でも修正は必要だが、以下の修正のみで動作している。VirtualBoxのバージョンは1.5.0。

--- linux-2.6.23-gentoo/drivers/net/tun.c.orig
+++ linux-2.6.23-gentoo/drivers/net/tun.c
@@ -483,9 +483,6 @@
 
 		err = -EINVAL;
 
-		if (!capable(CAP_NET_ADMIN))
-			return -EPERM;
-
 		/* Set dev type */
 		if (ifr->ifr_flags & IFF_TUN) {
 			/* TUN device */


ここで関係する権限「ケーパビリティ」については
http://www.atmarkit.co.jp/fsecurity/rensai/lids03/lids01.html
が参考になるかも知れない。
本来は、実行ファイルに対してCAP_NET_ADMINの権限を与えることが望ましいのだが、これを実現するためには、カーネルとlibcapに別のパッチを当てたりしないといけないようだ(非常に面倒だが、QEMUでの動作報告もあり、不可能ではない)。

iptablesのFORWARDチェインのポリシーがDROPの場合

(2007/7/24)iptablesでパケットフィルタリングをしていて、FORWARDチェイン*2のポリシーをDROP(破棄)にしていて、かつ、ゲストOSを固定IPアドレスにしている場合、

for i in [仮想マシンのIPアドレスリスト(スペース区切り)]
do
  iptables -A FORWARD -s $i -j ACCEPT
  iptables -A FORWARD -d $i -j ACCEPT
done

とすることで、仮想マシンから外部と通信ができるようになる。上の設定では、ポート番号ごとの設定などはしていないが、これはゲストOS側のファイアウォールで設定したほうがよい気がする。
(2007/8/1)もし、ホストOSのネットワーク上でDHCPが使用したい場合、送信元/宛先のIPアドレス不定なので

iptables -A FORWARD -i br0 -j ACCEPT

として、インターフェースbr0を使用する「経由」パケットを通す形で設定する。あるいは

iptables -A FORWARD -i br0 -p udp --sport 67:68 --dport 67:68 -j ACCEPT

DHCPだけ通すような設定を書いておいて、DHCPサーバ側で、仮想マシンMACアドレスと、貸し出すIPアドレスとを関連付ける(固定させる)設定をすることで、DHCPを使用しながらIPアドレスは固定させる、ということができるため、br0に対する経由の全許可をせずに、仮想マシンIPアドレスに対する許可設定ができる。
(2007/10/29)OSインストール中に(デフォルトで)DHCPを使用するDebianインストーラでは、DHCPサーバに接続するところでキャンセルするか、ホスト名設定で「戻る」をした後で手動設定を選択すると、IPアドレスを固定にする設定ができるので、DHCPサーバ側でMACアドレスIPアドレスを関連付けできない場合は、このようにするとよい。

使用したバージョン:

  • virtualbox-bin 1.4.0-r1
  • bridge-utils 1.2
  • usermode-utilities 20040406-r1
  • Linux(suspend2-sources) 2.6.21-r6

*1:正確には、権限の一種「CAP_NET_ADMIN」が必要

*2:受け取ったパケットの宛先が別マシンの場合に、通過させるかを判断するチェイン(パケットフィルタリングのルールの集まり)のこと