shd-tcp-toolsを用いたポートフォワーディング、ロードバランシング、レートリミティングの実装

 本稿で紹介するshd-tcp-toolsは、ポートフォワーディング(ポート転送)、ロードバランシング(負荷分散)、レートリミティング(転送速度制限)をTCP接続に施すためのツール群をまとめたものである。これが役立つのは、SSHサービスを提供したいが長時間に及ぶ単一のSCPオペレーションがサーバのインターネット接続を食い尽くしてしまわないよう、各ユーザが利用できる帯域幅に制限を設けておきたいという場合だ。

 確かにポートフォワーディング機能はSSH自身にも用意されているが、これにshd-tcp-toolsを併用することで、ポート転送に対するレートリミティングが可能となる。ただしshd-tcp-toolsはTCPレベルで動作し、認証および暗号化に関する有効な機能を有していないため、SSHのポートフォワーディング機能をshd-tcp-toolsにて置き換えられるという訳ではない。つまりSSHとshd-tcp-toolsは相互補完的な関係にあり、前者はセキュリティ関連の機能を提供し、後者はSSHを基にしたネットワーク容量のレートリミティング機能を提供し、可能であればキャパシティのロードバランシングも行うという住み分けになっているのだ。

 shd-tcp-toolsのパッケージは、Ubuntu、Fedora、openSUSEのリポジトリには収録されておらず、本稿の執筆においてもshd-tcp-toolsの最新版にあたるバージョン0.05のソースを入手した上で、後述する手順による64ビット版Fedora 9マシンを用いたソースからのアプリケーションビルドを行っている。なおshd-tcp-toolsはautotoolsを使用しないが、設定用のスクリプトは用意されており、そのMakefileもallおよびinstallをターゲットとするため、実質的にはautotoolsプロジェクトと同様のビルドが行える。具体的なインストール手順は下記のようになるが、ここで行う最終ステップはバイナリ群を/usr/local/binにインストールするMakefileの固定シーケンスである。

$ tar xjvf /.../shd-tcp-tools-0.05.tar.bz2
$ cd shd-tcp-tools-*
$ ./configure
$ make
$ sudo make install

 shd-tcp-toolsを構成する主要なツールは、単方向のTCPパイプを作成するtcppipe、TCPでの双方向ポートフォワーディングを可能とするtcp-pf、中間ホストを介したポートフォワーディングをセットアップするためのlistentwoおよびconnecttwoである。

 tcppipeツールの機能は、TCPを用いた単方向のデータ送信ないし受信において、転送速度に上限を課すことである。このコマンドを使用するには、転送するデータの送信元と受信先をパラメータ指定しなくてはならない。また送信元と受信先にそれぞれstdinおよびstdoutを用いる場合は、ハイフン記号を用いた省略表記もできる。

 下記に例示した2つのコードブロックは、クライアント側でTCP接続が確立されるまで待機してからサーバプロセスによるデータ送信が始まる場合もあるという想定の下、異なるターミナルから同時に進行させた実行例である。このうち1つ目のブロックがサーバ側にて実行したコマンド群、2つ目のブロックがクライアント側にて実行したコマンド群であり、ここでは前者で実行する個々のtcppipe転送コマンドの間にギャップを設けて、その間に後者のコマンドブロックにおけるクライアント側からのコマンド送信を行うようにした。

 1つ目のコマンドブロック、つまりサーバブロックで用いた1番目のtcppipeコマンドは、ローカルホストのポート5001に対する受信を開始させるための指定で、ここでは先に触れた-という指定を用いているため、tcppipeに送信されたデータはすべてstdoutへの出力とされる。同じく、2番目のtcppipeコマンドで用いている-pオプションは、進捗状況を表示させるための指定だ。ただし、この2番目のサーバコマンド実行例における送信データ量は1MBしかないので、表示すべき進捗情報は特になく、最終的な統計情報のみが出力されている。これに対して3番目のサーバコマンドによる実行例ではレートリミティングを施しているため、前回より1桁少ないデータ量しか転送させていないにもかかわらず、転送過程における進捗情報が出力されている。

$ tcppipe :5001 -
client connected from 0.0.0.0.0.0.0.0.0.0.0.0.0.0.255.255:57897
hi

$ tcppipe -p :5001 -
client connected from 0.0.0.0.0.0.0.0.0.0.0.0.0.0.255.255:52458
15729 ->: average bandwidth: 10.20 MiB/s
15729 ->: total bytes: 1048576

$ tcppipe -p :5001 -
client connected from 0.0.0.0.0.0.0.0.0.0.0.0.0.0.255.255:52460
->: bandwidth: 14.99 KiB/s	bytes: 30720
->: bandwidth: 10.01 KiB/s	bytes: 61440
->: bandwidth: 10.00 KiB/s	bytes: 81920
->: bandwidth: 9.99 KiB/s	bytes: 102400
15737 ->: average bandwidth: 11.11 KiB/s
15737 ->: total bytes: 102400

 先のサーバ側で用いた各コマンドに対応させる形でクライアント側にて実行させたのが、下記に一覧したコマンド群である。ただし1番目のコマンドに関しては-指定により送信元と受信先をそれぞれstdinとstdoutとしたデータコピーを行っているだけなので、対応するサーバコマンドは存在しない。以下、2番目のコマンドはローカルホストのポート5001に対するstdinのコピーを行わせたもの、3番目のコマンドは/dev/zeroからローカルホストのポート5001に対するデータ量1MBのコピーを行わせたもの、4番目のコマンドは--limitというパラメータ指定を用いて転送速度を10KBpsに制限したものである。ただしtcppipeでは若干のネットワークバッファリングが行われるため、この4番目の実行例でもddの処理終了までに要した時間は予想よりも短くなっており、データの書き出し速度も51KBps出ていたと表示されている。

$ echo hi | tcppipe - -
hi
$ echo hi | tcppipe - localhost 5001

$ dd if=/dev/zero  bs=1024 count=1024 | tcppipe - localhost 5001
1024+0 records in
1024+0 records out
1048576 bytes (1.0 MB) copied, 0.0806214 s, 13.0 MB/s

$ dd if=/dev/zero  bs=1024 count=100 | tcppipe --limit 10240 - localhost 5001
100+0 records in
100+0 records out
102400 bytes (102 kB) copied, 2.00108 s, 51.2 kB/s

 こうしたtcppipeが役立つのは1度行えば終わりといった単方向の転送であるが、次に解説するtcp-pfコマンドを用いると、双方向のポートフォワーディングをセットアップできる。SSHのポートフォワーディング機能にtcp-pfを併用することで得られるメリットの1つは、-rオプションの指定によりポートフォワーディングの速度制限ができることだ。

 下記に掲載したtcp-pfの実行例では、SSHデーモンに対するポートフォワーディング接続を確立させている。ここでは--rateオプションによるネットワーク転送速度を毎秒1,024バイトと指定しているが、これによりSSHシェルは相当分の低速化をするはずだ。-sオプションは、ローカルホストのポート10022をポートフォワーディングの送信元とする指定で、同様に-dは、ローカルホストのポート22をポートフォワーディングの送信先とするための指定である。ローカルホストのポート22にてSSH接続が行える限り、このtcp-pfコマンドを実行することでローカルホストのポート10022を用いたSSH接続が確立されて、レートリミティングを施したセッションが利用可能となる。なお最後に指定している-wオプションは、tcp-pfがサーバにアクセスして接続を確立するまでに待機させる時間(秒)の設定であり、私の経験でもこうした短めの遅延時間をtcp-pf側で設けておかないとSSH接続が正常に行われないようだ。

$ tcp-pf --rate 1024 -s 10022 -d 127.0.0.1:22 -w 1
client connected from 0.0.0.0.0.0.0.0.0.0.0.0.0.0.255.255:50715 => accept
16408 ->: bandwidth: 624.63 B/s	bytes: 1268
16408 <-: bandwidth: 888.23 B/s	bytes: 1804
16408 ->: bandwidth: 536.28 B/s	bytes: 2724
...
16408 ->: average bandwidth: 168.63 B/s
16408 ->: total bytes: 3524
16408 <-: average bandwidth: 860.33 B/s
16408 <-: total bytes: 17980
client handled

 なお-aないし--allowオプションを用いると、tcp-pfの送信元ポートへの接続を許可するホスト群の明示的な指定ができる。このオプションを用いた接続対象ホスト群の指定は何度も行えるが、逆に-aオプションの指定がない場合は任意のホストが接続可能となる。

 利用可能なApache Webサーバがいくつか存在するなど、同一のサービスが複数のサーバから提供されている場合は、tcp-pfの有す接続先サーバの自動選択機能を用いたロードバランシングが使用できる。その際の接続先ポート指定に用いるのは-pないし--dportオプションである。こうしたサーバのIPアドレス群を登録したファイルについては、その名前を-lないし--listオプションにて指定すればいい。このロードバランシングを用いる場合、各アドレスごとに割り当てられた負荷の大きさ(バイト単位)はtcp-pfコマンドにより自動管理されるので、ユーザが行うべき作業は該当するIPアドレスを1つのファイル中に記述しておくことだけだ。そしてこのファイルに登録したサーバ群に対しては、tcp-pfが送信したバイト量が各サーバのIPアドレスに続けて記録されていき、送信元のポートに新規のクライアントが接続すると、その段階にて送信量が最低のサーバがtcp-pfにより選択され、これがポートフォワーディングの対象サーバとされていくのである。

 最後に解説するlistentwoとconnecttwoは、ファイヤウォールで隔たれたホストへのポートフォワーディングを行うためのツールである。先に見たtcp-pfに対してconnecttwoの有す最大の相違点は、ローカルホスト以外の送信元ポートを指定できることだが、私が確認した限りの話においてこれらのツールは、デフォルト設定下および-hというコマンドラインオプションの使用に関する情報を何も提示してくれないのだ。またこれらは、レートリミティングもロードバランシングもサポートしていない。これらのツールを用いた基本的なTCPポートフォワーディングの設定法は複数存在するが、その詳細についてはshd-tcp-toolsに付属するreadmeファイルを参照して頂きたい。

 ポートフォワーディング用のコマンド群はSSHとの併用も行える。先に触れたように、SSH固有のポートフォワーディング機能を介してshd-tcp-toolsのポートフォワーディングを利用することは一見奇妙に感じられるかもしれないが、両者の組み合わせはshd-tcp-toolsのレートリミティング機能を利用可能とするし、1つしかない既存のSSHポート転送を基にしたポートフォワーディングを新規に複数セットアップすることも可能になるのだ。例えば「tcppipe :2002 -」というコマンドにより、サーバのstdoutにポート2002から任意の配信を可能とした後、下記に示した2つのコマンドをクライアント側にて実行したとしよう。このうちsshコマンドは、ローカルマシンのポート2001からサーバマシンのポート2002に向けてSSHによるポートフォワーディングを行わせるためのもので、その次のtcppipeコマンドは先に実行したものとよく似ているが、この場合はSSHのポートフォワーディングを介してポート2001のトラフィックをサーバ側のポート2002に送信するようにしている。

$ ssh -N -L 2001:localhost:2002 ben@server.example.com
$ echo hi | tcppipe - localhost 2001

 このサンプルコードだけを見る限り、メインの処理を賄っているのはSSHだけでshd-tcp-toolsを併用するメリットは何も感じられないかもしれないが、実は--limitオプションを用いることで、本来は1つしかないSSHポートフォワーディング接続を基にして複数のポートフォワーディングを構築し、これらの各ポートフォワーディングごとに異なるレートリミティングを施すことが可能となるのだ。

 通常shd-tcp-toolsはあらゆるネットワークインタフェースに接続できるが、特定のネットワークインタフェースのみに制限することも可能で、それにはexport INADDR_DEFAULT="pyx.example.com"のようにして環境変数INADDR_DEFAULTをエクスポートすればいい。

まとめ

 shd-tcp-toolsに属すツール群の間では、使用可能なコマンドラインオプションの一部に不整合性が見られる。例えばレートリミティングの指定にtcp-pfでは--rateオプションを使うのに対して、tcppipeでは--limitオプションを使うとreadme.txtには記述されている。ところが実際には--limitだけでなく--rateもtcppipeコマンドは受け付けるので、これら2つのツールに共通した転送速度制限の指定オプションとしては後者を使えばいいのである。また送信元と転送先の指定情報についても、tcppipeではコマンドラインの最終引数として記述するのに対して、tcp-pfでは-sおよび-dパラメータを用いた指定をしなくてはならない。

 前述したような不備は、バージョン番号を見る限りベータ版とみなしていい段階のツールであることを考えると、些末的な問題点だと言えるだろう。実際これらのユーティリティは、いずれも正常に機能して所定の目的を果たしてくれるのである。SSHでのアクセスを許可すると同時に、1人のユーザが利用可能とするネットワーク帯域幅を制限したいという場合に、個々のポートフォワーディング別にレートリミティングを個別設定できるというshd-tcp-toolsの存在が役に立つはずだ。

Ben Martinは10年以上にわたってファイルシステムに取り組んでおり、博士課程の修了後、現在はlibferris、ファイルシステム、検索ソリューションを中心としたコンサルティング業に従事している。

Linux.com 原文(2008年9月17日)