Solaris で SoftEther もどきを動かそう/其の1目次†ご注意†ここで紹介しているプログラムは、カーネルモジュールを含んでいます。そのため、プログラム上に問題があった場合(可能性大)にはシステムが PANIC し、場合によってはファイルシステムに重大な障害を引き起こす可能性があります。以下のプログラムはテストプ ログラムで、十分な検証を行ってはいません。したがってあくまで個人の検証ためにお使いいただき、重要なシステム上では動作させないでください。 お知らせ†(2005-02-19)†以前、仮想 NIC ドライバのドライバ名を "sfe" としていましたが、同じ名前の Solaris 用のフリーの NIC ドライバがあることが分かったため、名前を "ste" に変更いたしました。もし、検索サイトなどで、x86 Solaris 用の NIC ドライバを探してこのページに来られたかたがいらっしゃいましたら、お探しのドライバは http://homepage2.nifty.com/mrym3/taiyodo/eng/ のものかと思います。 概要とイメージ†今回はレイヤー2ベースの VPN アプリケーション作りに挑戦します。某コンピュータ誌の SoftEther の特集記事を読んで、このソフトの実装に大変興味を持ったのですが、現在のところ Windows 間でしか動作しないようだったので Solaris 上で同様の事ができるものを作ってみることにしました。(Linux 版等の動きもあるようですが、Solaris は予定にもなさそう・・泣)。 SoftEther で使われているプロトコルは今のところ公開されていないようなので、本プログラムでは SoftEther の仮想ハブに接続することはできません。本当はそれができれば面白いなぁと思っていたのですが・・・まぁ、プロトコルが公開されていても、それを実装するのは・・・難しいでしょう。とくに暗号通信の部分の実装とかは大変そうです。 ここで紹介するプログラムでは基本的に SoftEther と同一の構成をとっており、NIC や HUB をソフトウェアで仮想化することにより、レイヤ2での VPN を実現しています。それらは以下の3つのプログラムで構成されます。
各々のプログラムの役割や実装方法についても SoftEther の考えを取り入れています。
※ この図は分かりやすくするために簡略化して記述しています。 逆に SoftEther にあってこのプログラムに無いものは・・
無いものだらけです。(泣)仮想 NIC ドライバづくりで体力使い果たし、仮想ハブには力を注げませんでした。他のページで紹介しているプログラムもそうですが、基本的になんとか動くものを「作ってみる」ことを目標にしていますので、とりあえずこんなものかなぁと思ってます。 ソースファイルとダウンロード†
本プログラムは Sparc マシンの 64bit Solaris 9、Solaris 10 上で動作を確認しております。Solaris 8 でも動作すると思いますし、32 bit ドライバを作れば 32bit 環境でも動作すると思います。・・が、確認はしてはおりません。 解説†ste:仮想 NIC ドライバ†今回のプログラムのなかで一番苦労したのがこれです。実装法をなやんで二転三転し、試作品を大量に作ってしまいました。 まず、Solaris 上で動作する NIC のデバイスドライバを作ろうとした場合、大きく分けて以下の2つの方法があります。
GLD とは Solaris が提供しているネットワークインターフェースドライバーを開発するためのフレームワークで、ネットワークインターフェースドライバとしての必須機能である Stream や DLPI に関するほとんどの機能を gld(7D) ドライバーが提供してくれます。そのため、ドライバ開発者は、ハードウェアに依存した部分だけを記述するだけで、Solaris で動作するドライバーを作成することができます。x86 Solaris 用の NIC ドライバでも多く使われており、他の OS (主に Linux からでしょうか)からのドライバーの移植を容易にしてくれています。 今回作ろうとしているのは仮想 NIC のドライバで、もともとハード依存の部分なんか存在しないので、GLD を使えば非常に簡単に仮想 NIC のドライバが出来上がります。・・・ただし、GLD を使った場合、自由度という面では大きく制限を受けてしまいます。以下が、今回のドライバで GLD を使おうと考えたときに直面した問題点です。
つまりは、「そんな変な使い方すんな!」というところでしょうか。そんなわけで、GLD を使うことは断念し、ゼロから自前で作ることにいたしました。Solaris 上で動作するネットワークインターフェースドライバは DLPI バージョン2をサポートする必要があり、ゼロからのドライバを書く場合には、DLPI のサービスを全て自分で書かなければならなくなります。DLPI を提供するドライバを DLS プロバイダ(データリンクサービスプロバイダ)と呼びますが、実装によって以下の二つの種類があります。
簡単に言うと、「/dev/hme0」の様に「0」というインスタンス番号つきのデバイスを作成するのが Style 1 で、/dev/hme のようにインスタンス番号無しのデバイスを作成するのが Style 2 ということになります。普通よく目にするのが Style2 の方(インスタンス番号無し)ではないかと思います。これはつまり何が違うかというと、デバイスをオープンした時点でインスタンスが特定されるか、それともデバイスをオープン後に、アプリケーションが自分でインスタンスを指定するかの違いになります。(/dev/hme0 というデバイスがあればインスタンス0を使いたいのは明白ですよね?)どちらがいいか?というのははっきりわかりませんが、現在は Style 2 の方が一般的なようです。書かなきゃいけないコードで考えると、圧倒的に Style 1 の方が楽です。・・・ですが、ここはあえて一般的な Style 2 を採用します。 DLS プロバイダとして提供しなければならないサービスはたくさんあり、とても全てを実装できそうにありません。(sys/dlpi.h ファイル、OpenGroup のページ参照) そこで、まずは情報収集用のドライバを書いて ifconfig コマンドの plumb が実行されるときにドライバにどのような要求がくるかを調査しました。その結果、以下の DLPI プリミティブをサポートすれば、とりあえず通常の NIC ドライバの様に plumb できることがわかりました。
DLPI、プリミティブについてはSolaris DLPI プログラミングもあわせてご参照いただければと思います。
sted: 仮想 NIC デーモン†カーネルモードで動作する仮想 NIC ドライバと仮想ハブとの間のデータの中継を行うユーザモードプログラムです。仮想 NIC ドライバから受け取った Ethernet フレームは仮想 NIC デーモンによってカプセル化され、Socket を通じて仮想ハブに転送されます。また、おなじく Socket を通じて仮想ハブから受け取ったカプセル化された Ethernet フレームを開梱し、仮想 NIC ドライバに渡す役割も持ちます。 以下、プログラム内での実際の動きをご説明します。仮想 NIC デーモンは起動すると /dev/ste をオープンし、仮想 NIC のインスタンス(PPA)にアッタチした後、REGSVC という ste のオリジナルの IOCTL コマンドをドライバに発行します。REGSVC IOCTL コマンドを受け取った仮想 NIC ドライバは、仮想 NIC デーモンのストリームを登録し、そのストリーム以外からのメッセージをこの仮想 NIC デーモンのストリームに転送するようになります。 仮想 NIC デーモンは getmsg(2) システムコールを利用して、仮想 NIC ドライバからメッセージ(中身は Ethernet フレーム)を受け取ります。受け取った Ethernet フレームは 4 バイトの倍数になるようにパディングされた後、stehead 構造体という簡単なヘッダーを付加され、socket(3SOCKET) を使って仮想ハブに転送されます。 typedef struct stehead { int len; /* パディング後のデータサイズ */ int orglen; /* パディングする前のサイズ。 */ } stehead_t; 逆に、仮想ハブからデータを受信した場合には上記の stehead 構造体内のデータサイズ情報を元に Ethernet フレームを再構築し、putmsg(2) システムコールを使って、仮想 NIC ドライバに渡します。その後、仮想 NIC ドライバはあたかも実 NIC から Ethernet フレームを受け取ったように、それらを IP や ARP モジュールに渡します。 ※ ちなみに「デーモン」と名前がついていますが、端末の切り離しや、バックグラウンドでの 実行など、いわゆる「デーモンらしい」動作は行いません。その辺はまたいづれ・・・。 stehub: 仮想 ハブ†仮想ハブは、実際のハブのように、あるポート(仮想 NIC デーモンからの TCP 接続)からのパケットを他のポートへ転送する役割を持つます。 中身としては、ほとんど特筆すべきところはなく、TCP ポート 80 をリッスンしているサーバプログラムです。複数の仮想 NIC デーモンからの接続を管理し、また予想外の TCP 接続の切断を認識するために、各TCP 接続を conn_stat という構造体のリンクリストにて管理しています。 struct conn_stat { struct conn_stat *next; /* 次の conn_stat 構造体のポインタ */ int fd; /* FD */ struct in_addr addr; /* 接続してきている仮想 NIC デーモンのアドレス */ }; お粗末なコードなので、パケットの受け渡しがかなり非効率になってしまっているものと思います。要改善です。 インストール†コンパイル&インストール†コンパイルには gcc を使います。make をそのまま実行した場合 64 bit ドライバである ste と stehub, sted を作成します。 # make gcc ste.c -D_KERNEL -c -m64 /usr/ucb/ld -dn -r ste.o -o ste gcc sted.c -o sted -lsocket -lnsl gcc stehub.c -o stehub -lsocket -lnsl # ste ドライバのインストール、およびカーネルへのロード、ste デバイスノードの作成は make install で行います。 # make install /bin/cp ste /kernel/drv/sparcv9/ste /bin/cp ste.conf /kernel/drv/ste.conf /usr/sbin/add_drv ste # 仮想 NIC のデバイス名は ste になります。 # ls -l /dev/ste lrwxrwxrwx 1 root other 29 10月 28日 23:37 /dev/ste -> ../devices/pseudo/clone@0:ste # ls -l /devices/pseudo/clone@0:ste crw------- 1 root sys 11,215 11月 13日 23:58 /devices/pseudo/clone@0:ste # sted 、stehub デーモンについてはコピーは行われません。適当なところにコピーしてお使いください。 アンインストール†ドライバのアンインストールは make uninstall で行います。 # make uninstall /usr/sbin/rem_drv ste /bin/rm /kernel/drv/sparcv9/ste /bin/rm /kernel/drv/ste.conf # 同時に ste ドライバのアンロード、ste デバイスノードの削除も行われますが、ifconfig などで ste ドライバが利用中の場合、ドライバのアンロードは失敗します。この場合システムを再起動後に完全に削除されます。 # make uninstall /usr/sbin/rem_drv ste デバイスは使用中です Cannot unload module: ste Will be unloaded upon reboot. /bin/rm /kernel/drv/sparcv9/ste /bin/rm /kernel/drv/ste.conf # 使い方†仮想 NIC ドライバ、仮想 NIC デーモン、仮想ハブデーモンの使い方をご説明します。 仮想 NIC ドライバ†仮想 NIC のデバイス名は ste で、/dev/ste からアクセスできます。ste ドライバは実 NIC があるかのように振舞いますので、ifconfig や snoop コマンドで普通に利用できます。 ただし、MAC アドレスはデフォルト値が振られてしまいますので、ifconfig ehter コマンドにて、仮想ネットワーク内でユニークな MAC アドレスをわりあててやる必要があります。(下の例参照) # ifconfig ste0 plumb # ifconfig ste0 10.0.0.55 up # ifconfig ste0 ether 8:0:20:0:0:55 # ifconfig -a lo0: flags=1000849<UP,LOOPBACK,RUNNING,MULTICAST,IPv4> mtu 8232 index 1 inet 127.0.0.1 netmask ff000000 hme0: flags=1000843<UP,BROADCAST,RUNNING,MULTICAST,IPv4> mtu 1500 index 2 inet 172.29.73.55 netmask ffffff00 broadcast 172.29.73.255 ether 8:0:20:c6:69:c7 ste0: flags=1001843<UP,BROADCAST,RUNNING,MULTICAST,MULTI_BCAST,IPv4> mtu 1500 index 4 inet 10.0.0.55 netmask ff000000 broadcast 10.255.255.255 ether 8:0:20:0:0:55 # snoop -d ste0 Using device /dev/ste (promiscuous mode) 10.0.0.90 -> (broadcast) ARP C Who is 10.0.0.55, 10.0.0.55 ? 10.0.0.55 -> 10.0.0.90 ARP R 10.0.0.55, 10.0.0.55 is 8:0:20:0:0:55 10.0.0.90 -> 10.0.0.55 ICMP Echo request (ID: 543 Sequence number: 0) 10.0.0.55 -> 10.0.0.90 ICMP Echo reply (ID: 543 Sequence number: 0) # ste は実デバイスが存在しない仮想デバイスなので、/kernel/drv/ste.conf がその代わりとなります。(参照: man driver.conf(4)) デフォルトではインスタンス 0(=ste0)だけが有効なインスタンスになっています。 ste.conf ファイルの中身 name="ste" parent="pseudo" instance=0; 上記の instance=0 の「0」が仮想デバイスのインスタンスになります。 以下のようにインスタンス 1、インスタンス 2、という行を追加すればいくらでも仮想 ste デバイスが作ることができます。(ste1 や ste2 に相当) name="ste" parent="pseudo" instance=1; name="ste" parent="pseudo" instance=2; 仮想ハブ(プログラム名 stehub)†特に起動オプション等はありませんのでそのまま実行してください。そのまま実行した場合、フォアグラウンドで実行されてしまいますので、バックグラウンドで実行させたい「&」をつけて実行してください。 # ./stehub & [1] 312 # 仮想 NIC デーモン(プログラム名 sted)†起動時に ste デバイスのインスタンス番号と接続する仮想ハブのホスト名を指定します。なにも指定されなかった場合、デフォルトのインスタンス番号は 0(ste0)、デフォルトで接続するホスト名は localhost です。 Usage: ./sted [ -i instance] [-h hostname] # ./sted -i 0 -h hub.example.com & [1] 452 connected with HUB # 設定例†Host to Host†それぞれ別々の物理ネットワークに繋がった2つのホストを、仮想ハブを通じで、同一ネットワークに繋がっているように見せるための設定です。 以下の例では分かりやすくするために、仮想ハブと仮想 NIC デーモンが起動されているホストを別のホストにしていますが、仮想ハブは仮想 NIC デーモンが起動されているホスト上で動作させても問題ありません。
ホストAの設定†
ホストBの設定†
ホストCの設定†
Host to LAN†遠隔地にあるホストをローカルの LAN に参加させるための設定です。(たとえば、インターネット経由で自宅の LAN に参加する。)SoftEther では、Windows XP/2003 サーバに付属しているブリッジドライバを使って、仮想 NIC と実 NIC 間でブリッジ接続を構成することで実現させてるようです。Solaris にはこのような2つの NIC をブリッジ接続させるようなドライバーは提供されていませんので、「Solaris をスイッチングハブにしよう」でご紹介しているブリッジモジュール(brdg)を利用することにします。 ただし、brdg モジュールはもともとこのような使い方を想定していなかったので、ste ドライバと brdg モジュールの共生についてはかなり不安があります。ste ドライバ単体で使うよりもかなり危険(パニック、システムハング等)が高くなることをご承知の上お試しくださいませ。・・・一応動作が可能なことは確認しています。
ホストAの設定†
ホストBの設定†
ホストCの設定†
今後の課題†
|