Solaris をスイッチングハブにしよう/其の1目次 †ご注意 †ここで紹介しているプログラムは、カーネルモジュールです。そのため、プログラム上に問題があった場合にはシステムが panic し、場合によってはファイルシステムに重大な障害を引き起こす可能性があります。以下のプログラムはテストプログラムで、十分な検証を行ってはいません。したがってあくまで個人の検証ためにお使いいただき、重要なシステム上では動作させないでください。 概要とプログラムのイメージ †こんどは、余った Ethernet ポートを利用して、Solaris マシンを スイッチングハブ(Ethernet Switch) にしてしまおうというものです。Sparc マシンを使っていて、qfe(Sun の NIC で、1つのカードで4つの Ethernet ポートを持つ)を入れてるけど、実際には1つのポートしか使ってなくて、残りの3ポートがもったいない・・・なんて時は、このモジュールを使えば、使っていない Ethernet ポートをハブとして有効活用できます。きっとそんな人はいないと思いますけどね。(笑)もともとは、Firewall モジュール?の機能拡張として ステルスモード(ルーターとしてではなく、ブリッジとして動作するファイアーウォール)を追加しようとして書き始めたのですが、なんでか独立したモジュールになってしまいました。 プログラムとしては、パケットの転送を行う STREAMS モジュールと、設定用のコマンドの2つで構成されています。ちょうど以前書いた Firewall モジュール? と Solaris DLPI プログラミング?の両方の要素が混じったものです。まず、設定用コマンド(brdgadm)にてネットワークインターフェースのデバイスファイルをオープンして STREAM を作成し、そこに今回のモジュール(brdg モジュール)を PUSH します。ハブのポートとして追加したいネットワークインターフェースを設定用コマンドを使ってオープンし、brdg モジュールを PUSH することで、brdg モジュールのインスタンス 同士がパケットの交換を行い、結果としてパケットが他のポートに転送されることになります。
brdg モジュールはぶっちゃけパケットの転送をしているだけなので大した事はをしていません。が、brdgadm コマンドの方で行っている処理は少々特殊なことをやっていますので、そちらから説明します。以下が、brdgadm コマンドの流れです。
アッパーマルチプレクサーは、マルチプレクサーの開発者がそのよう作れば、プロセス側では単にマルチプレクサーをデバイスとしてオープンするだけで、複数の STREAM 間のルーティングができるようになりますが、ロアーマルチプレクサーの場合にはそうはいきません。マルチプレクサーの下方に STREAM を接続するためには STREAMS IOCTL(man streamio(7I))の I_LINK コマンドを発行する必要があります。
これにより、2つの STREAM(マルチプレクサーの STREAM とデバイスの STREAM )が接続されることになります。 次に、brdg モジュールですが、STREAMS モジュールとしての骨格は Firewall モジュール?とほぼ同じです、違うのはデータ(メッセージ)を受け取るときに呼ばれる put ルーチン内の処理です。 brdg モジュールではオープンされているネットワークインターフェース毎の情報を port_t という構造体で管理し、また、受け取ったパケットの発信元イーサネットアドレスを source_entry_t という構造体で保持しています。パケットを受け取った brdg モジュールは、これらの情報をもとに、どのネットワークインターフェースにパケットを転送すべきかを決定し、そのネットワークインターフェースドライバの write 側の put ルーチンにパケットを渡しています。パケットの処理の決定フローは以下のとおりです。 ソースファイルとダウンロード †ソースファイル: bridge-0.1.tar.gz
プログラムの解説 †brdgadm.c †コードの大部分は、基本的には Solaris DLPI プログラミング?で紹介している Packet Monitor のものと同じですのでこちらの解説を参考にしていただければと思います。brdgadm コマンドでは起動されるときのオプション(-a または -d)によって、add_interface() もしくは delete_interface() のどちらかが呼ばれることになります。 /* * SAP(Service Access Point)を 0 にして bind 要求 */ dlbindreq (if_fd, 0, 0, DL_CLDLS, 0, 0); dlbindack (if_fd, buf); /* * PROMISCOUS モードへ。DL_PROMISC_SAP、DL_PROMISC_PHYS どちらも ON */ dlpromisconreq(if_fd, DL_PROMISC_SAP); dlokack(if_fd, buf); dlpromisconreq(if_fd, DL_PROMISC_PHYS); dlokack(if_fd, buf); man hme によると、SAP 0 にバインドすることによって、Ethernet ヘッダのタイプフィールドが0〜1500の範囲のパケットが配信されるようになるとあります(下記)。しかしながら、バインド後にDL_PROMISC_SA フラグをセットしてプロミスキュアスモードリクエストを発行しているので、本当ならこのプロミスキュアスモードリクエストですべての SAP 値のパケットが配信されるはずなので、SAP 値0でバインドすることにどれくらい意味があるのかは分かりません。しかしながら、ここで SAP 0にバインドしておかないとパケットの受信ができません。
Packet Monitor では DL_PROMISC_PHY フラグをセットしてプロミスキュアスモードリクエストしていましたが、これだけではバインドしている SAP 値(Packet Monitor の場合 0x800=IPフレーム)のパケットしか配信されないため、ハブとして動作するためには不十分です。そのため、DL_PROMISC_SA フラグをセットしたプロミスキュアスモードリクエストも発行し、すべての SAP 値のパケットが配信されてくるようにしています。 /* * ip の stream に interface の stream を LINK * (PLINK = persist link) */ mux_id = ioctl(ip_fd, I_PLINK, if_fd); if( mux_id < 0){ perror("I_PLINK ioctl failed"); exit(0); } printf("mux_id = %d\n", mux_id); printf("Please write down mux_id.\nThis mux_id will be used for -d (=delete)\n"); add_interface() のなかに、先ほどお話した I_PLINK(パーシストリンク) ioclt コマンドを発行している部分がありますが、この ioctl() は返り値として mux_id という整数値を返してきます。この mux_id は IP マルチプレクサーが、接続されている STREAM を認識するためのユニークな ID で、後々、接続された STREAM を取り外すときに必要になります。先ほどパーシストリンクの説明のところでお話したように、brdgadm コマンドで作成された STREAM は関連したプロセスが存在しないので、ユーザはこの STREAM を制御することができず、STREAM を削除することもできません。 そのため、この STREAM を削除するためには、別途 IP の STREAM をオープンして、I_UNPLINK iolct コマンドを発行して IP マルチプレクサーにSTREAMを削除するように依頼するのです。このとき削除する STREAM を指定するためにこの muxid を与えます。 brdgadm コマンドでは -d オプションによって呼ばれる delete_interface() がこれを行っています。 if ((ip_fd = open("/dev/ip", O_RDWR)) < 0) syserr("/dev/ip"); if (ioctl(ip_fd, I_PUNLINK, mux_id) < 0) { perror("I_PUNLINK ioctl failed"); exit(0); } I_PUNLINK ioctl で指定している mux_id は brdgadm コマンドの -d オプションの引数として与えられるものです。これによりパーシストリンクしていたネットワークインターフェースの STREAM がアンリンク(リンク解除)されます。 brdg.c †brdg モジュールは brdgadm コマンドによって追加されたネットワークインターフェースを port_s という構造体で管理しています。port_s 構造体は brdgadm コマンドによって追加されたネットワークインターフェースのドライバの STREAM にある brdg モジュールのインスタンスの read queue のアドレスをメンバーとして持っています。 typedef struct port_s { queue_t *qptr; char *ifname; }port_t; port_t port_list[MAXPORT]; この port_s 構造体は MAXPORT を最大数とした配列になっていて0〜MAXPORTまでのネットワークインターフェースが追加できます。 また、brdg モジュールは受信したパケットの発信元 Ethernet アドレスを source_hash_table というハッシュテーブルで保持しています。ハッシュテーブルの各エントリは source_entry_s という構造体で、発信元 Ethernet アドレス、受信したネットワークインターフェースを示す port_t 構造体をメンバーとして持っています。これによって発信元 Ethernet アドレスを確認することで、パケットがどのネットワークインターフェースから入ってきたかが分かるので、受信したネットワークインターフェースに対して同じパケットを再度流すことを防ぐことができます。 typedef struct source_entry_s { struct ether_addr ether_source; port_t *port; int state; } source_entry_t; source_entry_t source_hash_table[MAXHASH]; ハッシュテーブルの最大値は MAXHASH で決まっており、現在は 1024 としています。 Ethernet アドレスからハッシュ値を生成しているので、まれに異なる2つの Ethernet アドレスが同一のエントリを指してしまうことがあり得ます。その場合コリジョンが発生した旨が syslog に通知されます。このような事態はあんまり発生しないとはおもいますが、頻発する場合には MAXHASH 値をもっと大きくする必要があります。 インストール †コンパイル&インストール †コンパイルには gcc を使います。make をそのまま実行した場合 64 bit モジュールである brdg と 32 bit モジュールである brdg32、brdgadm コマンドをを生成します。 # make /usr/local/bin/gcc -g -D_KERNEL -c brdg.c -o brdg32.o /usr/ucb/ld -dn -r brdg32.o -o brdg32 /usr/local/bin/gcc -g -D_KERNEL -c brdg.c -m64 /usr/ucb/ld -dn -r brdg.o -o brdg /usr/local/bin/gcc -g -lnsl -lsocket brdgadm.c -o brdgadm brdg モジュールのインストールおよびカーネルへのロードは make install で行います。 # make install /bin/cp brdg /kernel/strmod/sparcv9/brdg /usr/sbin/modload brdg 32 bit カーネルで起動されていて、32 bit モジュールをインストールする場合には make install32 を実行します。 # make install32 /bin/cp brdg32 /kernel/strmod/brdg /usr/sbin/modload brdg アンインストール †モジュールのアンインストールは make uninstall もしくは make uninstall32 で行えます。 # make uninstall /bin/rm /kernel/strmod/sparcv9/brdg /usr/sbin/modunload -i `/usr/sbin/modinfo | awk '/brdg/{ print }'` 同時に brdg モジュールのアンロードも行われますが、brdg モジュールを利用中の STREAM が存在していると、アンロードは失敗します。brdgadm コマンドを使って brdg モジュールを使っている STREAM をすべて削除するか、もしくはシステムを再起動してください。 # make uninstall /bin/rm /kernel/strmod/sparcv9/brdg /usr/sbin/modunload -i `/usr/sbin/modinfo | awk '/brdg/{ print }'` can't unload the module: Device busy make: *** [uninstall] Error 16 使い方 †インストールが終わってもモジュールのロードが終わっただけで、brdg モジュールはまだ利用可能状態にありません。brdgad コマンドをつかって、ハブのポートとして追加したいネットワークインターフェースを指定してください。 Usage: brdgadm [-a <interface> | -d mux_id ] インターフェースの追加 †インターフェースの追加には brdgad の -a オプションを使います。 例) # ./brdgadm -a hme0 devname = hme, ppa = 0 devpath = /dev/hme mux_id = 11 Please write down mux_id. This mux_id will be used for -d (=delete) # ./brdgadm -a le0 devname = le, ppa = 0 devpath = /dev/le mux_id = 12 Please write down mux_id. This mux_id will be used for -d (=delete) 上記の例では、hme0 と le0 をハブのポートとして追加しています。-a オプションが正常に終了すると、上記のプログラムの解説のところででお話した mux_id が出力されます。上の例ではパーシストリンクされた hme0 の STREAM の mux_id は「11」で、le0 の STREAM は「12」です。これらは次の「インターフェースの削除」で使われます。 インターフェースの削除 †インターフェースの削除を行うには brdgadm の -d オプションを使います。 例) # ./brdgadm -d 11 mux_id 11 is being deleted... Done # ./brdgadm -d 12 mux_id 12 is being deleted... Done
注意 †brdgadm コマンドで指定するネットワークインターフェースは ifconfig にて設定されていないインターフェースを想定しています。上記の例では ifconfig -a を実行すると、以下のようになり、hme0、le0 ともに 使われていないことが分かります。 # ifconfig -a lo0: flags=1000849 mtu 8232 index 1 inet 127.0.0.1 netmask ff000000 # ifconfig で plumb されている(UP/DOWNかかわらず)ネットワークインターフェースを brdgadm で追加した場合、予想外の自体が発生する恐れがあります。(予想外といっているのは、本当にどう挙動するか分からないとうだけです・・想像できる不具合があるってわけでは無いです・・どうということはないかもしれません) 今後の課題 †
|