TAPを使ったネットワークの利用方法


はじめに

es オペレーティングシステムの TCP/IP スタック ライブラリ(libesnet.a)はqemu上で実行中のesオペレーティングシステム上でも、 ツールビルドを行った Linux (Fedora Core) 上でもどちらからでも実行してテストすることができます。TCP/IPスタックの開発にあたっては現状マルチスレッドに対応したデバッガが使えるツールビルドを使用したほうが効率よく開発を進められるためです。

qemu では実際にネットワークにアクセスするために TUN/TAP デバイスを使用しています。ここでは、TAPを使って実際に LAN にアクセスできるように Fedora Core 6 を設定する手順について説明します。

注意: es-0.0.4 (es-0.0.4.tar.gz)以前のバージョンはネットワークに対応していません。

注意: 2007-10-31以降のソースコードではツールビルドではEthernetデバイスのエミュレーションにTUN/TAPのかわりにパケットインターフェイス(PF_PACKET)を使用するように変更しています。(参考: posix_ethernet.cpp)

注意: 以下はFC6で動作を確認した手順です。FC6以外のバージョンでは操作手順が異なる場合があるかもしれません。

概要

es カーネル(libeskernel.a)の Ethernet ドライバは IStream, IEthernet という2つのインターフェースを 提供することで、TCP/IPプロトコルスタックからLANにアクセスすることを可能にします。

下図は es オペレーティングシステム上でTCP/IPスタックを実行する場合のレイヤー構造を表しています。

    
   +------------------------+
   |     TCP/IP スタック    | (libesnet.a)
   +------------------------+
             ↑↓  (IStream, IEthernet)
   +------------------------+
   |      es カーネル       | (libeskernel.a)
   +------------------------+
             ↑↓  (仮想NE2000)
   +------------------------+
   |         QEMU           |
   +------------------------+
             ↑↓
   +------------------------+
   |   TAP デバイス tap0    |
   |       (Linux)          |
   +------------------------+
             ↑↓
   +------------------------+
   |  ブリッジデバイス br0  |
   |       (Linux)          |
   +------------------------+
             ↑↓
   +------------------------+
   | Ethernet デバイス eth1 |
   |        (Linux)         |
   +------------------------+
             ↑↓
   +------------------------+
   |          LAN           |
   +------------------------+
    

下図は Fedora Core 用のツールビルドからTCP/IPスタックを実行する場合のレイヤー構造を表しています。

    
   +------------------------+
   |     TCP/IP スタック    | (libesnet.a)
   +------------------------+
             ↑↓ (IStream, IEthernet)
   +------------------------+
   |      es カーネル       | (libeskernel.a)
   +------------------------+
             ↑↓ (posixインターフェイス) 
   +------------------------+
   |   TAP デバイス tap0    |
   |       (Linux)          |
   +------------------------+
             ↑↓
   +------------------------+
   |  ブリッジデバイス br0  |
   |       (Linux)          |
   +------------------------+
             ↑↓
   +------------------------+
   | Ethernet デバイス eth1 |
   |        (Linux)         |
   +------------------------+
             ↑↓
   +------------------------+
   |          LAN           |
   +------------------------+
    

どちらの場合でも TCP/IP スタックからは Ethernet デバイスは、es の名前空間 device/ethernet にバインドされたオブジェクトとしてまったく同じように利用することができます。es オペレーティングシステム上で動作している場合にはカーネルが NE2000 ネットワークカードを操作することによって qemu を介してネットワークにアクセスします。Fedora Core 用のツールビルド上で動作している場合にはカーネルが Linux の TAP デバイスを操作してネットワークにアクセスします。

このように TAP デバイスから LAN にアクセスできるようにするためには、TAP インターフェイス(tap0)と Ethernet アダプタのネットワーク インターフェース(eth1)をブリッジインターフェイス(br0)を介してブリッジ接続されるように Linux を設定しておく必要があります。

Linuxの設定

ここでは、Linux の PC に 2 枚のネットワークインターフェイスカードを挿入して、eth0 は通常のネットワークアクセス用途(Linux からのウェブ、メール、アップデート等)に、eth1 は es オペレーティングシステムの実験用に使用するものとして説明しています。eth1 には Linux から IP アドレスを割り当てないようにようにしてください。また FC6 ではデフォルトで IPv6 が有効になっている場合が多いかと思いますが、こちらも eth1 から Linux がパケットを送信することを避けるため無効にしてしてください。またプロトコルスタックの開発中には eth1 は eth0 とは独立した LAN に接続して、通常のネットワークオペレーションに支障をきたさないようにしておくべきでしょう。

TAP を使うためには、TUN/TAP を利用できるように Linux カーネルが構築されている必要がありますが、FC6 ではあらかじめそのように構築されているはずです。念のため、デバイス /dev/net/tun があることを確認してください。もし /dev/net/tun がなかった場合には次のコマンドを入力してノードを作成してください。

    
$ mknod /dev/net/tun c 10 200
    

続いてブリッジを準備します。下記のコマンドのように、ブリッジインターフェース(br0)を作ってeth1に接続します。 もし brctl コマンドが見つからない場合には $ yum install bridge を実行してみてください。

    
$ /usr/sbin/brctl addbr br0
$ /usr/sbin/brctl addif br0 eth1
$ /sbin/ifconfig br0 up
$ /sbin/ifconfig eth1 0.0.0.0 up
    

このままでは Linux をリブートするたびにブリッジの設定は消えてしまいますので、必要に応じて設定してください。

TAP起動スクリプトの作成

TAP インターフェース(tap0)は、QEMU および Fedora Core 用の es カーネルの実行中に作成され、終了時には削除されてしまいます。そのため、tap0 を br0 にブリッジ接続するために tap0 が作成されたときにユーザーの指定したスクリプトが (/etc/qemu-ifup) 呼び出されるようになっています。

/etc/qemu-ifup の記述例を以下に示します。

    
 #!/bin/sh
 
 echo "Executing /etc/qemu-ifup"
 echo "Bringing up $1 for bridged mode..."
 sudo /sbin/ifconfig $1 0.0.0.0 promisc up
 echo "Adding $1 to br0..."
 sudo /usr/sbin/brctl addif br0 $1
 sleep 16
    

このスクリプトは、(引数$1として渡される) tap0 をブリッジインターフェース (br0) に接続します。上記のスクリプトではブリッジ接続が安定するまで16秒待つようにしてあります。

注意: 私たちの環境では、tap0 が起動してから約15秒間の間、 「受信はできるが送信はできない」という状態になりました。 回避策としてこのウェイトを入れています。

以上で Linux の設定は完了です。なお、上記のスクリプトやコマンドの実行に当たってはルート権限が必要なことにも注意してください。

QEMU で TAP を利用する手順

QEMU から TAP ネットワーク インターフェイスを使う際には、通常のオプションに加えて以下のオプションを指定します。

    
-net nic,vlan=0 -net tap,vlan=0,ifname=tap0,script=/etc/qemu-ifup
    

テストスイートの qemu 起動スクリプトを使用している場合には環境変数 QFLAGS に上記のオプションを設定しておけば、qemu 実行時に追加のコマンドライン引数として渡されます。

    
$ export QFLAGS="-net nic,vlan=0 -net tap,vlan=0,ifname=tap0,script=/etc/qemu-ifup"
    

Fedora Core 用のツールビルドで TAP を利用する手順

現在のところFedora Core のツールビルドではスクリプト名はlibeskernel.aのソースコードposix_init.cpp中に直接埋め込んでいます。上記の記述どおりに設定された場合には特に起動オプション等はなしに TAP を利用できます。

TCP/IP スタックのテスト

TCP/IP スタックのテストプログラムconfig.cppを実行してみてください。実際にTAP経由でネットワークに接続するときはルート権限が必要なことに注意してください。

    
[net]$ testsuite/config
    

このテストプログラムは Ethernet インターフェイスに IPv4 アドレス 192.168.2.40 を割り当てようとします。実際に割り当てができたら、外部のノードに ping を送信します。うまく行けば、Wireshark などを使って次のようなパケットが LAN 上に流れることを確認できるはずです。


No.     Time        Source                Destination           Protocol Info
      1 0.000000    52:54:00:12:34:56     ff:ff:ff:ff:ff:ff     ARP      Who has 192.168.2.40?  Tell 0.0.0.0
      2 2.848413    52:54:00:12:34:56     ff:ff:ff:ff:ff:ff     ARP      Who has 192.168.2.40?  Tell 0.0.0.0
      3 5.696882    52:54:00:12:34:56     ff:ff:ff:ff:ff:ff     ARP      Who has 192.168.2.40?  Tell 0.0.0.0
      4 7.706856    52:54:00:12:34:56     ff:ff:ff:ff:ff:ff     ARP      Who has 192.168.2.40?  Gratuitous ARP
      5 7.798952    52:54:00:12:34:56     ff:ff:ff:ff:ff:ff     ARP      Who has 192.168.2.1?  Tell 192.168.2.40
      6 7.799704    00:50:f2:c4:b1:9e     52:54:00:12:34:56     ARP      192.168.2.1 is at 00:50:f2:c4:b1:9e
      7 7.812635    192.168.2.40          192.195.204.26        ICMP     Echo (ping) request
      8 8.039455    192.195.204.26        192.168.2.40          ICMP     Echo (ping) reply
      9 8.055931    192.168.2.40          192.195.204.26        ICMP     Echo (ping) request
     10 8.283059    192.195.204.26        192.168.2.40          ICMP     Echo (ping) reply
     11 8.300092    192.168.2.40          192.195.204.26        ICMP     Echo (ping) request
     12 8.526512    192.195.204.26        192.168.2.40          ICMP     Echo (ping) reply
     13 9.716453    52:54:00:12:34:56     ff:ff:ff:ff:ff:ff     ARP      Who has 192.168.2.40?  Gratuitous ARP
  

config.cpp プログラムは、まず Ethernet インターフェイスを IInternetConfig インターフェイスの addInterface メソッドを使って TCP/IP プロトコルから利用できるように登録を行っています。


// Setup DIX interface
Handle<IStream> ethernetStream = context->lookup("device/ethernet");
Handle<IEthernet> nic(ethernetStream);
nic->start();
int dixID = config->addInterface(ethernetStream, ARPHdr::HRD_ETHERNET);

続いて Ethernet インターフェイスにホストアドレス(192.168.2.40)を IInternetConfig インターフェイスの addAddress メソッドを使って割り当てています。その後、8秒間待っていますが、これは ARP でアドレスの衝突がないかどうか確認するのを待つためのものです。


// Register host address (192.168.2.40)
InAddr addr = { htonl(192 << 24 | 168 << 16 | 2 << 8 | 40) };
Handle<IInternetAddress> host = resolver->getHostByAddress(&addr.addr, sizeof addr, dixID);
config->addAddress(host, 16);
esSleep(80000000); // Wait for the host address to be settled.

es ではインターネットアドレスを表現するために IInternetAddress インターフェイスを用いています。新しい IInternetAddress オブジェクトを取得するときは IResolver インターフェイスの getHostByAddress メソッドを使用します。

さらにデフォルト ゲートウェイ(192.168.2.1)を IInternetConfig インターフェイスの addRouter メソッドを使って割り当てています。


// Register a default router (192.168.2.1)
InAddr addrRouter = { htonl(192 << 24 | 168 << 16 | 2 << 8 | 1) };
Handle<IInternetAddress> router = resolver->getHostByAddress(&addrRouter.addr, sizeof addr, dixID);
config->addRouter(router);

つづいて、ループバックアドレスに対して ping (ICMP Echo リクエスト) を送信しています。getHostByAddress メソッドの最後の引数 1 は、ループバックインターフェイスの既定のスコープIDです。


Handle<IInternetAddress> loopback = resolver->getHostByAddress(&InAddrLoopback.addr, sizeof addr, 1);
loopback->isReachable(10000000);

さらに続けて、ループバックインターフェイスを使って UDP パケットの送受信を行っています。esでは、自ホストにアサインされているアドレスに対して socket メソッドを呼び出すと、自ホストにバインドされた新しいソケットを得ることができます。


// Test bind and connect operations
Handle<ISocket> socket = loopback->socket(AF_INET, ISocket::DGRAM, 53);
socket->connect(loopback, 53);

// Test read and write operations
char output[4] = "xyz";
socket->write(output, 4);
char input[4];
socket->read(input, 4);
esReport("'%s'\n", input);

// Test close operation
socket->close();

また LAN の外側のホストに対して ping を投げることもできます。手順はループバックに対して行ったのと同様です。


// Test remote ping (192.195.204.26 / www.nintendo.com)
InAddr addrRemote = { htonl(192 << 24 | 195 << 16 | 204 << 8 | 26) };
Handle<IInternetAddress> remote = resolver->getHostByAddress(&addrRemote.addr, sizeof addr, 0);
esReport("ping #1\n");
remote->isReachable(10000000);
esReport("ping #2\n");
remote->isReachable(10000000);
esReport("ping #3\n");
remote->isReachable(10000000);

[esオペレーティングシステムのホームページに戻る]


Copyright © 2006, 2007
Nintendo Co,. Ltd.

Permission to use, copy, modify, distribute and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appears in all copies and that both that copyright notice and this permission notice appear in supporting documentation. Nintendo makes no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty.

SourceForge.jp