仙石浩明の日記

stone 開発日記

2011年6月2日

Android 端末上で透過型プロキシを動かしてみる 〜 VPN のように使えて、しかも省電力

透過型プロキシ (Transparent Proxy) というのは、 ブラウザから 「見えない」 プロキシのこと。 ブラウザ自身は WWW サーバにアクセスしているつもりなのに、 ブラウザが送信したリクエストをプロキシが横取りし、 プロキシから出し直す。 サーバからのレスポンスは当然プロキシに返り、 プロキシがそれをブラウザに送信するのだけど、 パケットがブラウザに届くまでの間に送信元アドレスが書き換えられて、 サーバから直接レスポンスが届いたようにブラウザからは見える。

フツーの 「見える」 プロキシは、 ブラウザ等でプロキシ設定が必要であるのに対し、 透過型プロキシだと設定が不要。 だから一部の ISP (インターネット接続プロバイダ) などで、 フツーのプロキシの代りに使われていたりする (ユーザにプロキシ設定の方法を説明する必要がなくてサポートコストが削減できる)。 あるいは企業等で、 従業員が仕事と関係ない Web ページを閲覧していないか監視するために、 社内から社外へ接続するゲートウェイ等に透過型プロキシを設置して、 社外への http アクセスを記録していたりする (監視だけならパケットをダンプするだけでも用が足りるが、 アクセス先のサイト毎に細かな制御を行なおうとすると、 プロキシを使った方が楽)。

このように、 透過型プロキシはその存在を隠すために使われることが多いが、 ブラウザにプロキシ設定の機能が無い場合は、 透過型プロキシを使わざるを得ない。 例えば、 Android 端末で使われるブラウザの多くが、 なぜかプロキシ設定の機能を持っていない。 プロキシ経由でアクセスされると、 モバイル端末からのアクセスなのか、 フツーの PC からのアクセスなのか、 区別がつかなくなってしまうので、 あえてプロキシ設定できないようにしているのかも知れないが、 プロキシを使わないとアクセスできない場合は困ってしまう。

私の場合、 勤務先の LAN 内のサーバに社外からアクセスするとき、 社内アクセス専用のプロキシ (ここでは仮にホスト名を proxy.klab.org とする) を利用している。 例えば senri.gcd.org (自宅のサーバ、つまり社外) からこのプロキシをアクセスすると、 こんな感じ:

senri:~ $ openssl s_client -connect proxy.klab.org:443 -cert cert.pem -key key.pem -CApath /usr/ssl/certs -quiet
Enter pass phrase for key.pem:xxxxxxxx

depth=1 /C=US/O=Equifax/OU=Equifax Secure Certificate Authority
verify return:1
depth=0 /serialNumber=-cn4oMJtlqoqfQZaTat68U68dNVbM8iQ/C=JP/O=*.klab.org/OU=GT41256819/OU=See www.rapidssl.com/resources/cps (c)09/OU=Domain Control Validated - RapidSSL(R)/CN=*.klab.org
verify return:1
CONNECT irc.klab.org:6667 HTTP/1.1

HTTP/1.0 200 OK

:irc.klab.org NOTICE AUTH :*** Looking up your hostname...
:irc.klab.org NOTICE AUTH :*** Found your hostname

行末に 「」 がある行が入力行。 「」 は Enter キー押下。 秘密鍵 key.pem でクライアント認証を受けて proxy.klab.org に接続し、 CONNECT リクエストを送信することにより、 社内の任意のサーバ:ポートに接続できる。 上記の例では社内 IRC サーバである irc.klab.org:6667 に接続しているが、 もちろん社内の任意の WWW サーバにも接続できる。

説明を簡単にするため proxy.klab.org と書いたが、 実際には VPN Warp を使っている (だから proxy.klab.org というサーバは存在しない)。 VPN Warp も stone で接続することができるので、 以下の説明は VPN Warp の場合もほとんど同様に適用することができる。

プロキシというと、 ファイアウォールの内側から外部のインターネットをアクセスするために用いるものと思っている人が多いせいか、 外からファイアウォールの内側をアクセスするためのプロキシは、 特にリバースプロキシ (reverse proxy) と呼ばれることもある。

proxy.klab.org を透過型プロキシとして利用できれば、 社内の任意のサーバに透過的にアクセスできるようになる。 例えばこんな感じ:

senri:~ $ adb shell
$ getprop ro.build.description
soju-user 2.3.4 GRJ22 121341 release-keys
$ su
# busybox traceroute irc.klab.org
traceroute to irc.klab.org (10.10.0.18), 30 hops max, 38 byte packets
 1  110.158.20.29 (110.158.20.29)  701.203 ms  90.517 ms  85.570 ms
 2  *  *  *
 3  *  *  *
 4  110.158.18.138 (110.158.18.138)  79.540 ms !A  *  *
 5  *  *  *
 6  *  ^C
senri:~ $ adb shell
$ telnet irc.klab.org 6667
:irc.klab.org NOTICE AUTH :*** Looking up your hostname...
:irc.klab.org NOTICE AUTH :*** Found your hostname

soju-user 2.3.4 GRJ22」 つまり Android 2.3.4 な Nexus S で、 パケットが届かないはずの irc.klab.org (10.10.0.18) に対して、 telnet でアクセスできている。 telnet でアクセスできるということは、 もちろん任意の IRC アプリで irc.klab.org にアクセスできるということ。

VPN (Virtual Private Network) でも同じことができる! という声が聞こえてきそうだが、 VPN だと TCP でデータを送受信していないときも、 VPN セッションを張っているだけでいろんなパケット (例えば ARP) が行き交って、 そのたびに電波が飛んで電池を消耗するので、 あまりモバイル向きではないと思う。

あるいは、 ConnectBot などを使って ssh で port forward を行なう方法もあるが、 これまた ssh セッションを張りっぱなしだと電池消耗が心配だし、 かといって IRC を使う前に毎回 ssh 接続を行なうのはメンドクサイ。 また、 通信先/宛先ポートが増えるたび port foward 設定を追加するのもメンドクサイ。

その点、 透過型プロキシだと実際にパケットが飛ぶときのみ通信が行なわれるので、 電池消耗をあまり心配せずに TCP セッションを張りっぱなしにできる。 また、 LAN 内の通信先/宛先ポートが増えても設定変更は不要。 モバイルで LAN 内にアクセスする方法として最適だと思う (便利なのに普及していないのはナゼ?)。

もっと読む...
Filed under: Android,stone 開発日記 — hiroaki_sengoku @ 09:41
2007年11月22日

x86_64 Linux でメモリ・デバッグ・ツール Valgrind を使う場合の注意点

次のようなプログラム test.c について考える:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>

struct test {
    int32_t len;
    int8_t buf[16];
};

int main(int argc, char *argv[]) {
    struct test *p = malloc(sizeof(struct test));
    int8_t buf[16];
    p->len = sizeof(p->buf);
    bzero(p->buf, p->len);
    printf("0x%lX-0x%lX => 0x%lX\n",
	   (long)p->buf, (long)p->buf+p->len-1, (long)buf);
    bcopy(p->buf, buf, p->len);
    free(p);
    return 0;
}

malloc(3) で確保した領域のうち、 16 byte を bcopy(3) でコピーするだけの極めて単純なプログラムであり、 特に問題はないように見える。

ところが memory debugging tool Valgrind を使って検証してみると、 x86_64 Linux だと次のようなエラーが出てしまう。

sag16:/home/sengoku/tmp % cc -O -Wall test.c
sag16:/home/sengoku/tmp % valgrind ./a.out
==19008== Memcheck, a memory error detector.
==19008== Copyright (C) 2002-2006, and GNU GPL'd, by Julian Seward et al.
==19008== Using LibVEX rev 1658, a library for dynamic binary translation.
==19008== Copyright (C) 2004-2006, and GNU GPL'd, by OpenWorks LLP.
==19008== Using valgrind-3.2.1-Debian, a dynamic binary instrumentation framework.
==19008== Copyright (C) 2000-2006, and GNU GPL'd, by Julian Seward et al.
==19008== For more details, rerun with: -v
==19008==
0x4D5C034-0x4D5C043 => 0x7FF000750
==19008== Invalid read of size 8
==19008==    at 0x4B9326B: (within /lib/libc-2.3.6.so)
==19008==    by 0x4B92C06: bcopy (in /lib/libc-2.3.6.so)
==19008==    by 0x4005BD: main (in /home/sengoku/tmp/a.out)
==19008==  Address 0x4D5C040 is 16 bytes inside a block of size 20 alloc'd
==19008==    at 0x4A1B858: malloc (vg_replace_malloc.c:149)
==19008==    by 0x400574: main (in /home/sengoku/tmp/a.out)
==19008==
==19008== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 8 from 1)
==19008== malloc/free: in use at exit: 0 bytes in 0 blocks.
==19008== malloc/free: 1 allocs, 1 frees, 20 bytes allocated.
==19008== For counts of detected errors, rerun with: -v
==19008== All heap blocks were freed -- no leaks are possible.

「Invalid read of size 8」、 すなわちアクセスすべきではないメモリを、 64bit (8 byte) 読み込み命令で読んだというエラー。

test.c で読み込みを行なう可能性があるところと言えば、 「bcopy(p->buf, buf, p->len);」の部分だけであり、 その範囲は printf で表示しているように、 0x4D5C034 番地から 0x4D5C043 番地までの 16 byte である。

ところが、Valgrind 曰く:

Address 0x4D5C040 is 16 bytes inside a block of size 20 alloc'd

ちょっと英語の意味が取りにくい (私の英語力が低いだけ? ^^;) が、 つまり「malloc で確保した 20 byte の領域のうち、 先頭から数えて 16 byte 目 (先頭は 0 byte 目と数える) が 0x4D5C040 番地であり、 この番地に対してメモリ読み込みが行なわれた」 という意味である (「16 byte 目」なら 「16 bytes」でなくて「16th byte」のような...?)。

すなわち、 「20 byte の領域のうち 16 byte 目」というのは残り 4 byte であり、 あと 4 byte コピーすればいいのにもかかわらず、 64bit 読み込み命令を使って 8 byte いっぺんに読んでしまっているから、 malloc で確保した領域の外をアクセスしてしまう、というわけ。

結果として 4 byte 無駄に読んでしまっている (実はコピー開始位置も 4 byte 前から行なうので、計 8 byte 余計に読み込んでいる) わけだが、 CPU にとって一番高速にコピーできる単位が (64bit 境界に合わせた) 64bit 読み書きだから、 bcopy の実装がこのようになっているのだろう。

より正確に言えば、 bcopy は 16 byte 以上のコピーを行なう場合は コピー開始位置手前の 64bit 境界 (alignment) の番地から 64bit ずつコピーし、 16 byte 未満の場合は byte 単位でコピーする。 test.c では、 コピー開始位置 p->buf が (直前のメンバが int32_t なので) 64bit 境界に一致しておらず、 しかもコピーする byte 数 p->len が 16 byte (= 64bit の倍数) なので、 16 byte 以上のコピーかつコピー終了位置も 64bit 境界に一致していない、 というのがミソである。

したがって 32bit な x86 Linux の場合であれば 32bit 単位でコピーを行なうので、 test.c ではこのようなエラーは起きない。 もちろん、64bit な x86_64 Linux で Valgrind がエラーを出すからといって、 bcopy の x86_64 における実装に問題がある、というわけではない。 Valgrind は、 あくまでバグの「可能性」を指摘するだけであって、 malloc で確保した領域の外へのアクセスでも、 それが意図的なものであれば (メモリ保護違反などでない限り) 何の問題もない。

分かってみれば単純な話なのであるが、 Valgrind のメッセージ「16 bytes inside a block」の意味が把握できなかった私は、 glibc の bcopy のソースを読んで 64bit 単位でコピーを行なっていることを知り、 4 byte の領域外読み込みが行なわれることを理解して初めて、 Valgrind のメッセージの意味が分かったという、 本末転倒な体験をした (^^;)。

ちなみに、 もちろん最初から上記のようなテストプログラムを Valgrind で チェックしようと思ったわけではなく、 「struct test」構造体は実際には次のような SockAddr 構造体であり、 saDup 関数にて malloc した SockAddr 構造体を doconnect 関数で bcopy する処理になっていて、 元ネタは拙作 stone である。

typedef struct {
    socklen_t len;
    struct sockaddr addr;
} SockAddr;
#define SockAddrBaseSize	((int)&((SockAddr*)NULL)->addr)
...

SockAddr *saDup(struct sockaddr *sa, socklen_t salen) {
    SockAddr *ret = malloc(SockAddrBaseSize + salen);
...

int doconnect(Pair *p1, struct sockaddr *sa, socklen_t salen) {
    struct sockaddr_storage ss;
    struct sockaddr *dst = (struct sockaddr*)&ss;	/* destination */
...
    bcopy(sa, dst, salen);
...

stone ML にて、 Valgrind で検証したらエラーが出た、という報告を頂いて (_O_) 以上のような調査を行なった次第。 bcopy に与えた引数に問題はなく、 どうしてこれが 「Invalid read of size 8」 エラーを引き起こすのか謎だった。 結果的には stone には問題はなく、 修正の必要もないことが判明したわけであるが、 今まで使っていなかった Valgrind を使ってみるいいきっかけになった。 実を言うと 64bit Linux を (プログラミングのレベルで) 使ったのも、 今回が初めてだったりする (^^;)。

Filed under: stone 開発日記,プログラミングと開発環境 — hiroaki_sengoku @ 20:36
2007年6月25日

stone に Server Name Indication (TLS 拡張) 機能を実装

RFC 3546 で、 TLS (Transport Layer Security つまり SSL) の拡張が規定された。 その中の一つが、「Server Name Indication」と呼ばれる拡張であり、 クライアントがサーバに対して、 サーバのホスト名を伝えることができるようになった (規定されたのは 2003年6月なのであるが、まだあまり普及していない)。

なぜクライアントがサーバへ、 当のサーバの名前を伝えてやる必要があるかというと、 サーバが複数のホスト名を持つ場合があるからだ。 例えば WWW サーバは、 http リクエスト中の「Host:」フィールドを見てレスポンスを切り替える。 この機能はバーチャルドメインと呼ばれ、 一つの IP アドレスで複数のホスト名のサービスを提供する方法として、 広く使われている。

ところが (従来の) https の場合、この方法が使えない。 WWW サーバは SSL 通信を開始するにあたって、 *最初に*サーバ証明書を クライアントへ送る必要があるからだ。 http リクエスト中の「Host:」フィールドに、 別のホスト名が書いてあったとしても後の祭。 「リクエストしたホスト名と、 サーバから送られてきた証明書に記載されたホスト名が一致しない」という旨の 警告が WWWブラウザに表示されてしまう。

もう少し詳しく説明すると、 クライアント (WWWブラウザ) とサーバは、 SSL 通信を始めるにあたって、 次のようなハンドシェークを行なう。

クライアント サーバ
ClientHello 乱数, セッションID, 暗号/圧縮方式
ServerHello 乱数, セッションID, 暗号/圧縮方式決定
Certificate サーバ証明書
ServerKeyExchange 共通鍵の交換
(CertificateRequest クライアント認証を要求する時のみ)
ServerHelloDone ServerHello の終了を通知
(ClientCertificate クライアント認証を要求された時のみ)
ClientKeyExchange 共通鍵の交換
ChangeCipherSpec 次のデータから暗号化することを通知
Finished 以上のハンドシェークのハッシュ値(暗文)
ChangeCipherSpec 次のデータから暗号化することを通知
Finished 以上のハンドシェークのハッシュ値(暗文)

このハンドシェークの後、 クライアントが暗号化された http リクエストを送信し、 それを受けてサーバが暗号化されたレスポンスを返す。

https サーバがバーチャルドメイン機能を持つには、 https サーバがサーバ証明書を送信する (上のハンドシェーク図の 3行目) より前に、 クライアントがリクエストしたいホスト名を通知する必要がある。 上図から明らかなように、 ホスト名の通知は一番最初の「ClientHello」で行なわれなければならず、 そのための拡張が、 「Server Name Indication」というわけである。 もちろんこの時点では、まだ鍵の交換は行なわれていないので、 ホスト名は平文で送られる。

前置きが長くなってしまったが、 この Server Name Indication (SNI) を stone でサポートしてみた (stone.c Revision 2.3.1.11 以降)。 ただし stone が利用している OpenSSL で SNI がサポートされるのは 0.9.9 以降である (追記: 0.9.8f 以降でもサポートされた) ので、 OpenSSL 0.9.9 以降のライブラリを使って stone を make する必要がある 。

二台の http サーバ senri.gcd.org と asao.gcd.org があるとき、 次のように stone を実行する:

stone -z sni \
      -z servername=senri.gcd.org \
          -z cert=senri.gcd.org-cert.pem \
          -z key=senri.gcd.org-key.pem \
          senri.gcd.org:http 443/ssl -- \
      -z servername=asao.gcd.org \
          -z cert=asao.gcd.org-cert.pem \
          -z key=asao.gcd.org-key.pem \
          asao.gcd.org:http 443/ssl

転送元ポート指定「443/ssl」が二度現われていることに注意。

最初の「senri.gcd.org:http 443/ssl」は、 「-z servername=senri.gcd.org」と指定しているように、 クライアントが通知するサーバのホスト名が senri.gcd.org の場合の指定である。 サーバ (つまり stone) は、 「-z cert=ファイル名」と「-z key=ファイル名」で指定されるサーバ証明書を返し、 クライアントからの通信を、 SSL 復号を行なった上で senri.gcd.org:http へ中継する。

二番目の「asao.gcd.org:http 443/ssl」についても同様に、 クライアントが asao.gcd.org を通知すれば、 stone は asao.gcd.org のサーバ証明書を返すとともに、 クライアントからの通信を、 SSL 復号を行なった上で asao.gcd.org:http へ中継する。

この例では http サーバは二台のみであるが、 同様に何台でも指定できる。 また、もちろん物理的に異なるサーバを用意する必要があるわけではなく、 一台の http サーバで複数のポートを開き、 各ポートで別々のホスト名のサービスを提供してもよい。

OpenSSL 0.9.9 の s_client コマンド (SSL クライアント) でアクセスしてみると、 senri.gcd.org を通知すれば (-servername senri.gcd.org オプション)、

% openssl s_client -connect localhost:443 -servername senri.gcd.org -CApath /usr/local/ssl/certs
CONNECTED(00000003)
depth=2 /C=JP/ST=Kanagawa/L=Kawasaki/O=GCD/CN=GCD Root CA/emailAddress=root@gcd.org
verify return:1
depth=1 /C=JP/ST=Kanagawa/L=Kawasaki/O=GCD/OU=Hiroaki Sengoku/CN=GCD_Sengoku_CA/emailAddress=sengoku@gcd.org
verify return:1
depth=0 /C=JP/ST=Kanagawa/L=Kawasaki/O=GCD/OU=Hiroaki Sengoku/CN=senri.gcd.org/emailAddress=sengoku@gcd.org
verify return:1
Server did acknowledge servername extension.

サーバ (stone) は「CN=senri.gcd.org」の証明書を返し、 同じ 443番ポートへのアクセスでも 通知するサーバ名を asao.gcd.org へ変えるだけで、

% openssl s_client -connect localhost:443 -servername asao.gcd.org -CApath /usr/local/ssl/certs
CONNECTED(00000003)
depth=2 /C=JP/ST=Kanagawa/L=Kawasaki/O=GCD/CN=GCD Root CA/emailAddress=root@gcd.org
verify return:1
depth=1 /C=JP/ST=Kanagawa/L=Kawasaki/O=GCD/OU=Hiroaki Sengoku/CN=GCD_Sengoku_CA/emailAddress=sengoku@gcd.org
verify return:1
depth=0 /C=JP/ST=Kanagawa/L=Kawasaki/O=GCD/OU=Hiroaki Sengoku/CN=asao.gcd.org/emailAddress=sengoku@gcd.org
verify return:1
Server did acknowledge servername extension.

サーバが返す証明書が「CN=asao.gcd.org」に変わるし、 stone が中継する先も asao.gcd.org:http へ変わる。 したがって一つの IP アドレスに 複数のホスト名を持たせるバーチャルドメイン機能を、 任意の SSL 通信で実現できる。

なお、上記 stone 実行方法 (オプション指定) は、やや煩雑なので、

stone -z sni \
      -z certpat=%n-cert.pem \
      -z keypat=%n-key.pem \
      -z servername=senri.gcd.org \
          senri.gcd.org:http 443/ssl -- \
      -z servername=asao.gcd.org \
          asao.gcd.org:http 443/ssl

などと、証明書ファイルの指定をまとめることもできる。 「-z certpat=%n-cert.pem」オプションによって証明書のファイルのパターンを 指定する。 「%n」はサーバのホスト名で置き換えられる。 すなわち、 「-z servername=senri.gcd.org」を指定した場合は、 「-z cert=senri.gcd.org-cert.pem」を指定したのと同じ結果になる。 「-z keypat=%n-key.pem」についても同様。

現時点で SNI をサポートしている WWWブラウザは、 私の知っている範囲だと Firefox 2.0 等と IE7 だけであるが、 今後は開発される WWWブラウザの大半が SNI をサポートすることになるだろう。 そうなれば https サーバも、 バーチャルドメインで運用することが一般的となるはずである。

Name Based SSLについてもうちょっと腰を入れて調べる。
IE7ではRFC3546 のServer Name Indicationの対応がされている。
Apacheの方では、RFC3546は、mod_gnutlsを入れれば対応は可能なようです。
mod_gnutlsのサイトはこちら。
が、2005年から時が止まったまま・・・。むう。

OpenSSL 0.9.9 の安定版がリリースされれば (追記: SNI をサポートした 0.9.8f が 10/11 にリリースされた)、 apache などの WWW サーバにおいても SNI サポートが普通になると思われるが、 それまでは stone で SSL 暗号化を行なうようにすれば、 手軽に SNI を利用できる。 サーバに OpenSSL 0.9.9 をインストールしてしまうと、 OpenSSL を利用する全てのソフトウェアが影響を受けてしまうが、 例えば以下のように stone.c をコンパイル (Linux でのコンパイル例) して、 stone だけ 0.9.9 をリンクするようにすれば、 影響を stone だけに限定できる。

% cc -Wall -DCPP='"/usr/bin/cpp -traditional"' \
     -DPTHREAD -DUNIX_DAEMON -DPRCTL -DSO_ORIGINAL_DST=80 -DUSE_POP -DUSE_SSL \
     -I /usr/local/ssl-0.9.9/include -L /usr/local/ssl-0.9.9/lib \
     -o stone stone.c -lpthread -ldl -lssl -lcrypto

以上は stone が SSL サーバとして、 サーバホスト名通知を受付ける場合であるが、 もちろん stone を SSL クライアントとして実行し、 サーバホスト名を通知することもできる。

% stone -q sni -q verbose -q verify -q CApath=/usr/local/ssl/certs
        -q servername=asao.gcd.org localhost:443/ssl 10080
Jun 23 08:43:46.116894 3084876480 start (2.3c) [18500]
Jun 23 08:43:46.185448 3084876480 stone 3: 127.0.0.1:443/ssl <- 0.0.0.0:10080
Jun 23 08:43:48.610513 3084876480 3 TCP 6: [depth2=/C=JP/ST=Kanagawa/L=Kawasaki/O=GCD/CN=GCD Root CA/emailAddress=root@gcd.org]
Jun 23 08:43:48.610707 3084876480 3 TCP 6: [depth1=/C=JP/ST=Kanagawa/L=Kawasaki/O=GCD/OU=Hiroaki Sengoku/CN=GCD_Sengoku_CA/emailAddress=sengoku@gcd.org]
Jun 23 08:43:48.610928 3084876480 3 TCP 6: [depth0=/C=JP/ST=Kanagawa/L=Kawasaki/O=GCD/OU=Hiroaki Sengoku/CN=asao.gcd.org/emailAddress=sengoku@gcd.org]
Jun 23 08:43:48.624724 3084876480 [SSL cipher=AES256-SHA]

「-q servername=asao.gcd.org」オプションで、 通知するサーバホスト名を指定している。 この実行例の場合「asao.gcd.org」を通知しているので、 サーバからは「CN=asao.gcd.org」の証明書が返されている。 この実行例では、中継先が「localhost:443」であるので 「-q servername=」オプションを指定する必要があるが、 もし中継先 (サーバ) ホスト名が通知すべきサーバホスト名に一致するのであれば、 「-q servername=」オプションは省略できる。

Filed under: stone 開発日記 — hiroaki_sengoku @ 07:20
2007年6月15日

stone に UDP ⇔ TCP 相互変換機能を実装

stone で UDP と TCP の相互 変換を実装してみた (stone.c 2.3.1.10以降)。 つまり、UDP パケットが届いたら、 その頭にデータ長 (2バイト) を付けて 次図の形式のデータにして TCP セッションへ送信する。 あるいは逆に、 TCP セッションから次図の形式のデータを受信したら、 「データ長」の部分を取り除いて 「可変長データ」の部分を UDP パケットとして送信する。

┌──┬──┬──┬─≪─┬──┐
│データ長 │ 可変長データ  │
└──┴──┴──┴─≫─┴──┘

もちろん「データ長」はネットワーク バイトオーダ (ビッグエンディアン)。 この形式は、 DNS の問合わせ/応答を TCP を使って行なう方法 (RFC1035 4.2.2. TCP usage) と 同じであるので、

# stone -n 192.168.1.1:domain/udp localhost:domain
Jun 15 06:34:35.215996 16384 start (2.3c) [15707]
Jun 15 06:34:35.248158 16384 stone 3: 192.168.1.1:53/udp <- 127.0.0.1:53

などと stone を実行 (TCP 53 を UDP 53 へ変換) しておいて、

% host -T www.gcd.org localhost
Using domain server:
Name: localhost
Address: 127.0.0.1#53
Aliases:

www.gcd.org has address 60.32.85.220
www.gcd.org has address 60.32.85.221
www.gcd.org has address 60.32.85.216

などと TCP で localhost への問合わせ (-T オプション) を行なうことができる。 すなわち、stone が TCP での問合わせを UDP に変換して ネームサーバ (192.168.1.1:53/udp) へ中継している。

あるいは逆に、

# stone -n 192.168.1.1:domain localhost:domain/udp
Jun 15 06:38:36.660967 16384 start (2.3c) [18576]
Jun 15 06:38:36.662647 16384 stone 3: 192.168.1.1:53 <- 127.0.0.1:53/udp

などと stone を実行 (UDP 53 を TCP 53 へ変換) しておいて、

% host www.klab.org localhost
Using domain server:
Name: localhost
Address: 127.0.0.1#53
Aliases:

www.klab.org has address 211.13.209.203

などと UDP で localhost へ問合わせることができる。 すなわち、stone が UDP の問合わせを TCP に変換して ネームサーバ (192.168.1.1:53) へ中継している。

この UDP TCP 相互変換機能を、 ssh や VPN-Warp などを使った (TCP) ポートフォワードと組合わせることにより、 UDP のポートフォワードが可能になる。

Filed under: stone 開発日記 — hiroaki_sengoku @ 09:05
2007年1月4日

La Fonera 上で stone を走らせてみる

La Fonera (FON ソーシャル ルータ) のファーム ウェアのソース コードは、 Fon blog に書かれているように、 http://download.fon.com/firmware/fonera/latest/fonera.tar.bz2 から取得できる。 これを展開して make を実行すると、 「OpenWrt Configuration」ダイアログが表示される。 現行の La Fonera のファーム ウェアと同じものをビルドするだけなら Configuration の変更は不要。そのまま save して exit すればビルドが行なわれる。

stone を make するには、 libpthread と libopenssl が必要なので、 以下のように設定する。
まず、

     Base system  --->

を選択して、以下のように libpthread を make するように指定する:

<*> libpthread.......................................... POSIX threa

続いて、

    Libraries  --->

を選択して、以下のように libopenssl も make するように指定しておく:

<M> libopenssl..................................... Open source SSL
< >   openssl-util.............................. OpenSSL command lin
<M> zlib................. Library implementing the deflate compressi

「Do you wish to save your new OpenWrt configuration?」 と聞かれるので「Yes」と答える。 するとクロスコンパイル環境とファームウェアのビルドがどんどん進む。

*** End of OpenWrt configuration.
*** Execute 'make' to build the OpenWrt or try 'make help'.

make[2] toolchain/install
make[3] -C toolchain install
make[4] -C toolchain/sed prepare
make[4] -C toolchain/sed compile
make[4] -C toolchain/sed install
make[4] -C toolchain/kernel-headers prepare
make[4] -C toolchain/kernel-headers compile
make[4] -C toolchain/kernel-headers install
make[4] -C toolchain/sstrip prepare
make[4] -C toolchain/sstrip compile
make[4] -C toolchain/sstrip install
make[4] -C toolchain/uClibc prepare
make[4] -C toolchain/binutils prepare
make[4] -C toolchain/binutils compile
make[4] -C toolchain/binutils install
make[4] -C toolchain/gcc prepare
make[4] -C toolchain/gcc compile
make[4] -C toolchain/uClibc compile
make[4] -C toolchain/uClibc install
make[4] -C toolchain/gcc install
make[4] -C toolchain/ipkg-utils prepare
make[4] -C toolchain/ipkg-utils compile
make[4] -C toolchain/ipkg-utils install
make[4] -C toolchain/libnotimpl prepare
make[4] -C toolchain/libnotimpl compile
make[4] -C toolchain/libnotimpl install
make[4] -C toolchain/lzma prepare
make[4] -C toolchain/lzma compile
make[4] -C toolchain/lzma install
make[4] -C toolchain/squashfs prepare
make[4] -C toolchain/squashfs compile
make[4] -C toolchain/squashfs install
make[4] -C toolchain/jffs2 prepare
make[4] -C toolchain/jffs2 compile
make[4] -C toolchain/jffs2 install

「install」と表示されているが、 これは「./staging_dir_mips」ディレクトリへのインストール。 「./staging_dir_mips」にクロスコンパイル環境がインストールされた後に、 これを使ってカーネルやライブラリ等のコンパイルが進む。

make[2] target/compile
make[3] -C target compile
make[4] -C target/utils prepare
make[4] -C target/utils compile
make[4] -C target/utils install
make[4] -C target/linux prepare
make[5] -C target/linux/ar531x-2.4 prepare
make[4] -C target/linux compile
make[5] -C target/linux/ar531x-2.4 compile
make[6] -C target/linux/ar531x-2.4 modules
make[6] -C target/linux/ar531x-2.4 packages
make[4] -C target/image/ar531x compile
make[2] package/compile
make[3] -C package compile
make[4] -C package compile-targets
make[5] -C package/base-files compile
make[5] -C package/bridge compile
make[5] -C package/busybox compile
make[5] -C package/chillispot compile
make[5] -C package/dnsmasq compile
make[5] -C package/dropbear compile
make[5] -C package/foncheckrsa compile
make[5] -C package/haserl compile
make[5] -C package/madwifi compile
make[5] -C package/hostapd compile
make[5] -C package/iptables compile
make[5] -C package/mini_fo compile
make[5] -C package/mtd compile
make[5] -C package/libpcap compile
make[5] -C package/linux-atm compile
make[5] -C package/ppp compile
make[5] -C package/pptp compile
make[5] -C package/iproute2 compile
make[5] -C package/qos compile
make[5] -C package/webif compile
make[5] -C package/wireless-tools compile
make[5] -C package/zlib compile
make[5] -C package/openssl compile

「OpenWrt Configuration」ダイアログで指定しなかった パッケージまで表示されるが、 これは単に make がそのパッケージのディレクトリの Makefile を実行しただけで、 コンパイルなどは何も行なわずに抜けている。

make[2] package/install
make[3] -C package install
make[4] -C package install-targets
make[5] -C package/base-files install
make[5] -C package/bridge install
make[5] -C package/busybox install
make[5] -C package/chillispot install
make[5] -C package/dnsmasq install
make[5] -C package/dropbear install
make[5] -C package/foncheckrsa install
make[5] -C package/haserl install
make[5] -C package/hostapd install
make[5] -C package/iptables install
make[5] -C package/madwifi install
make[5] -C package/mini_fo install
make[5] -C package/mtd install
make[5] -C package/ppp install
make[5] -C package/pptp install
make[5] -C package/qos install
make[5] -C package/iproute2 install
make[5] -C package/webif install
make[5] -C package/wireless-tools install
make[2] target/install
make[3] -C target install
make[4] -C target/image/ar531x clean
make[5] -C target/image/generic/lzma-loader clean
make[4] -C target/utils prepare
make[4] -C target/utils compile
make[4] -C target/utils install
make[4] -C target/linux prepare
make[5] -C target/linux/ar531x-2.4 prepare
make[4] -C target/linux compile
make[5] -C target/linux/ar531x-2.4 compile
make[4] -C target/linux install
make[5] -C target/linux/ar531x-2.4 install
make[4] -C target/image/ar531x compile
make[4] -C target/image/ar531x install
make[5] -C target/image/generic/lzma-loader clean compile

以上で、クロスコンパイル環境とファームウェアのビルドが全て完了。 所要時間は 30分ほど (私の Celeron D 345 マシンの場合)。

ビルドしたクロスコンパイル環境は、 「staging_dir_mips/bin」に PATH を通すだけで使用できる。

stone を make してみる。 まずは CVS レポジトリから最新版を checkout する:

senri % cvs -d:pserver:anonymous@cvs.sourceforge.jp:/cvsroot/stone login
Logging in to :pserver:anonymous@cvs.sourceforge.jp:2401/cvsroot/stone
CVS password:
senri % cvs -z3 -d:pserver:anonymous@cvs.sourceforge.jp:/cvsroot/stone co stone
cvs checkout: Updating stone
U stone/GPL.txt
U stone/Makefile
U stone/README.en.txt
U stone/README.txt
U stone/cryptoapi.c
U stone/dist
U stone/global.h
U stone/logmsg.mc
U stone/md5.h
U stone/md5c.c
U stone/stone.1
U stone/stone.1.ja
U stone/stone.c
U stone/stone.cnf
U stone/stone.sh
U stone/stone.spec

次に make する:

senri % cd stone
senri % make fon-ssl
make CC="mips-linux-uclibc-gcc" SSL_LIBS="-lssl -lcrypto" TARGET=fon ssl_stone
make[1]: Entering directory `stone'
make FLAGS="-DUSE_POP -DUSE_SSL " LIBS=" -lssl -lcrypto" fon
make[2]: Entering directory `stone'
make CC="mips-linux-uclibc-gcc" FLAGS="-Wall -DPTHREAD -DUNIX_DAEMON -DPRCTL -DUSE_POP -DUSE_SSL " LIBS="-lpthread -lssl -lcrypto" stone
make[3]: Entering directory `stone'
mips-linux-uclibc-gcc  -Wall -DPTHREAD -DUNIX_DAEMON -DPRCTL -DUSE_POP -DUSE_SSL  -o stone stone.c -lpthread -lssl -lcrypto
make[3]: Leaving directory `stone'
mips-linux-uclibc-strip stone
make[2]: Leaving directory `stone'
make[1]: Leaving directory `stone'

なお、CVS から checkout した最新版でなくても、 例えば stone 2.3c において

senri:/usr/src/stone-2.3c % mips-linux-uclibc-gcc  -Wall -DPTHREAD -DUNIX_DAEMON -DPRCTL -DUSE_POP -DUSE_SSL -o stone stone.c -lpthread -lssl -lcrypto

などとコンパイルすることにより La Fonera 版 stone を make することができる。 つまり特に変更することなく make できるわけで、 いかに La Fonera がフツーの Linux マシンであるかが分かる。

make した stone および、 libpthread, libopenssl を La Fonera へコピーすれば (libopenssl は SSL 版 stone の場合のみ必要)、 La Fonera 上で stone を実行することができる。

root@OpenWrt:~# stone
Jan  4 00:18:08.478674 1024 start (2.3c) [14946]
Jan  4 00:18:08.488958 1024 stone 2.3c  http://www.gcd.org/sengoku/stone/
Jan  4 00:18:08.570372 1024 Copyright(C)2007 by Hiroaki Sengoku <sengoku@gcd.org>
Jan  4 00:18:08.579479 1024 using OpenSSL 0.9.8d 28 Sep 2006  http://www.openssl.org/
Usage: stone <opt>... <stone> [-- <stone>]...
opt:  -h opt            ; help for <opt> more
      -h stone          ; help for <stone>
      -h ssl            ; help for <SSL>, see -q/-z opt
Filed under: La Fonera,stone 開発日記 — hiroaki_sengoku @ 09:32
2006年11月29日

stone 2.3c のバグ (select 版の make 方法)

stone 2.3c epoll 版 (つまり -DUSE_EPOLL をつけてコンパイル) において、 proxy 機能にバグを見つけた。 stone を http proxy として使用して、 同じセッション内で異なるホスト (IP アドレスが異なる) へ アクセスすると接続できずにタイムアウトする。 ブラウザで「再読み込み」操作を行なえば、 ブラウザが新規セッションを張るので接続できるようになる。

stone.c 2.2.4.7 にて修正済み。 同一セッションにて異なるホストへ接続しようとしたとき、 stone は旧ソケットをクローズして新しいソケットをオープンするのだが、 このとき EPOLL_CTL_ADD するのを忘れていた。 select 版では新ソケットで FD_SET するので問題はない。

なお、epoll 版は Linux カーネルおよび glibc にて epoll がサポートされていることが必要。 現在使われている Linux ディストリビューションの大半 (?) は、 いまだ EPOLLONESHOT をサポートしていないので、 epoll 版を make することはできず、 次のようなエラーになる:

% make linux-ssl
make TARGET=linux ssl_stone LIBS="-ldl"
make[1]: Entering directory `stone'
make FLAGS="-DUSE_POP -DUSE_SSL -I/usr/local/ssl/include " LIBS="-ldl -L/usr/local/ssl/lib -lssl -lcrypto" linux
make[2]: Entering directory `stone'
make FLAGS="-Wall -DCPP='\"/usr/bin/cpp -traditional\"' -DPTHREAD -DUNIX_DAEMON -DPRCTL -DSO_ORIGINAL_DST=80 -DUSE_EPOLL -DUSE_POP -DUSE_SSL -I/usr/local/ssl/include " LIBS="-lpthread -ldl -L/usr/local/ssl/lib -lssl -lcrypto" stone
make[3]: Entering directory `stone'
cc  -Wall -DCPP='"/usr/bin/cpp -traditional"' -DPTHREAD -DUNIX_DAEMON -DPRCTL -DSO_ORIGINAL_DST=80 -DUSE_EPOLL -DUSE_POP -DUSE_SSL -I/usr/local/ssl/include  -o stone stone.c -lpthread -ldl -L/usr/local/ssl/lib -lssl -lcrypto
stone.c: In function `healthCheck':
stone.c:1687: error: `EPOLLONESHOT' undeclared (first use in this function)
stone.c:1687: error: (Each undeclared identifier is reported only once
stone.c:1687: error: for each function it appears in.)
stone.c: In function `asyncConn':
stone.c:3517: error: `EPOLLONESHOT' undeclared (first use in this function)
stone.c: In function `getident':
stone.c:3703: error: `EPOLLONESHOT' undeclared (first use in this function)
stone.c: In function `proxyCommon':
stone.c:5054: error: `EPOLLONESHOT' undeclared (first use in this function)
stone.c: In function `proto2fdset':
stone.c:5699: error: `EPOLLONESHOT' undeclared (first use in this function)
stone.c: In function `doAcceptConnect':
stone.c:6277: error: `EPOLLONESHOT' undeclared (first use in this function)
stone.c: In function `asyncClose':
stone.c:6350: error: `EPOLLONESHOT' undeclared (first use in this function)
make[3]: *** [stone] Error 1
make[3]: Leaving directory `stone'
make[2]: *** [linux] Error 2
make[2]: Leaving directory `stone'
make[1]: *** [ssl_stone] Error 2
make[1]: Leaving directory `stone'
make: *** [linux-ssl] Error 2

このようなエラーが出る場合は、 Makefile にて 「-DUSE_EPOLL」 を削除して make することにより、 select 版を作成できる。

% diff -u Makefile.org Makefile
--- Makefile.org	Tue Nov 28 13:52:54 2006
+++ Makefile	Tue Nov 28 13:52:09 2006
@@ -95,7 +95,7 @@
 	$(MAKE) FLAGS="-DNT_SERVICE $(FLAGS) $(POP_FLAGS) $(SSL_FLAGS)" LIBS="$(LIBS) $(SSL_LIBS) $(SVC_LIBS) -ladvapi32 -luser32 -lgdi32 -lshell32 -lkernel32" $(TARGET)
 
 linux:
-	$(MAKE) FLAGS="-Wall -DCPP='\"/usr/bin/cpp -traditional\"' -DPTHREAD -DUNIX_DAEMON -DPRCTL -DSO_ORIGINAL_DST=80 -DUSE_EPOLL $(FLAGS)" LIBS="-lpthread $(LIBS)" stone
+	$(MAKE) FLAGS="-Wall -DCPP='\"/usr/bin/cpp -traditional\"' -DPTHREAD -DUNIX_DAEMON -DPRCTL -DSO_ORIGINAL_DST=80 $(FLAGS)" LIBS="-lpthread $(LIBS)" stone
 
 linux-pop:
 	$(MAKE) TARGET=linux pop_stone

このようなパッチを Makefile にあてた後、make すればよい。

% make linux-ssl
make TARGET=linux ssl_stone LIBS="-ldl"
make[1]: Entering directory `stone'
make FLAGS="-DUSE_POP -DUSE_SSL -I/usr/local/ssl/include " LIBS="-ldl -L/usr/local/ssl/lib -lssl -lcrypto" linux
make[2]: Entering directory `stone'
make FLAGS="-Wall -DCPP='\"/usr/bin/cpp -traditional\"' -DPTHREAD -DUNIX_DAEMON -DPRCTL -DSO_ORIGINAL_DST=80 -DUSE_POP -DUSE_SSL -I/usr/local/ssl/include " LIBS="-lpthread -ldl -L/usr/local/ssl/lib -lssl -lcrypto" stone
make[3]: Entering directory `stone'
cc  -Wall -DCPP='"/usr/bin/cpp -traditional"' -DPTHREAD -DUNIX_DAEMON -DPRCTL -DSO_ORIGINAL_DST=80 -DUSE_POP -DUSE_SSL -I/usr/local/ssl/include  -o stone stone.c -lpthread -ldl -L/usr/local/ssl/lib -lssl -lcrypto
make[3]: Leaving directory `stone'
make[2]: Leaving directory `stone'
make[1]: Leaving directory `stone'
Filed under: stone 開発日記 — hiroaki_sengoku @ 06:37
2006年8月18日

stone 2.3c リリース

stone 2.3c を リリースした。 現時点ではスナップショット扱いであるが、重大なバグが発見されない限り、 このバージョンを stone 2.3 に代えて正式リリースとする予定 (今度こそ!)。 stone 2.3b からの 変更点は以下の通り

depth 値に依存せずに root CA 識別名を指定できるように

stone の SSL 認証 (3) で説明したように、 root CA から数えた階層数で「re数字=正規表現」を指定できるようにした。 root CA を「-1」とし、root CA の子CA が「-2」、以下順に数字が減っていく。

busy loop が起きるバグを修正

stone 2.3b のバグ」 に書いたように、 stone 2.3b には SSL 接続が確立する前に TCP 接続が切れてしまうと、 まれに busy loop が発生するバグがあった。 stone.c 2.2.3.12 で修正済み。

さらに、万一 busy loop が起きたときに、それを検知するコードを追加した。 stone.c 2.2.3.13 には、 busy loop の誤検出するケースがあったので、 stone.c 2.2.3.14 で修正した。

SSL_accept 前に EOF となった場合にエラーを出さない

TCP レベルのヘルスチェックを行なっている場合、 SSL プロトコルを喋らずに shutdown する設定で運用するケースがある。 このような場合に、stone が SSL エラーとして出力してしまうと、 ログが無意味に肥大化してしまう。 そこで accept 直後に EOF になった場合は DEBUG メッセージのみ出力し、 エラーは出力しないようにした (stone.c 2.2.3.13)。

SSL close notify を送信できていなくてもエラーを出さない

SSL close notify を送信する前に通信相手が接続を切ってしまうケースがある。 あまり好ましいことではないが、 SSL セッションの再利用ができなくなる点を除けば実害はなく、 行儀が悪い通信相手のためにエラーログを肥大化させるのは割が合わないので、 これもエラーメッセージから DEBUG メッセージへ降格した (stone.c 2.2.3.13)。

「/udp」指定を転送元、転送先とで区別

従来は、転送先あるいは転送元のどちらかに「/udp」を指定していれば、 udp の中継を行なっていた。 今後は、udp の中継を行なう場合は、両方に「/udp」を指定する必要がある。 片方のみ「/udp」を指定した場合は、 udp から tcp への、あるいはその逆の変換を行なう指定とする予定(未実装)。

Filed under: stone 開発日記 — hiroaki_sengoku @ 08:25
2006年8月15日

stone の SSL 認証 (3)

前々回前回で 説明したように、 stone で 通信相手の SSL 認証を行なうには、 通信相手がクライアントの時 (クライアント認証) は、

-z verify -z CApath=ディレクトリ

という設定で実行し、通信相手がサーバの時 (サーバ認証) は、

-q verify -q CApath=ディレクトリ

という設定で実行する。 「CApath=...」で指定したディレクトリに、 root CA 証明書を「ハッシュ値.数字」という形式のファイル名で 置いておくことにより、 通信相手が提示した証明書を検証することができ、 検証に成功したときのみ通信を許可する、 などのアクセス制限を行なうことができる。

ある root CA が発行した証明書、 あるいはその root CA の子, 孫, ... CA が発行した証明書、 いずれの証明書でも内容を問わず通信を許可して構わないのであれば、 これ以上の設定は不要であるが、 現実問題としては正規の証明書であれば何でも OK というケースは稀だろう。

メジャーな root CA だと、沢山の子 CA を持ち、 それぞれの子がさらに沢山の孫 CA を持っていたりする。 root CA 以外の CA を中間 CA と呼ぶが、 沢山の中間 CA がそれぞれ沢山の証明書を発行しているわけであるから、 その証明書の全てを許可していては、 不特定多数との通信を許可するのと大差ない。

そこで SSL 認証では、証明書を検証するだけでなく、 証明書の内容をも確認することが必要となる。 stone では証明書の識別名が満たすべき条件を正規表現の形で指定することができる。 証明書の識別名とは、例えば次のようなものである:

C=JP
ST=Kanagawa
L=Kawasaki
O=GCD
OU=Hiroaki Sengoku
CN=test
emailAddress=sengoku@gcd.org

これを「/」で区切って一行にした文字列:

/C=JP/ST=Kanagawa/L=Kawasaki/O=GCD/OU=Hiroaki Sengoku/CN=test/emailAddress=sengoku@gcd.org

この一行で表現した識別名が満たすべき正規表現を設定する。 この形式は、 前々回説明したクライアントが 既知の root CA (の子 CA) が発行した証明書を提示した場合の stone の出力例の 中に出てきている:

depth2=/C=JP/ST=Kanagawa/L=Kawasaki/O=GCD/CN=GCD Root CA/emailAddress=root@gcd.org
depth1=/C=JP/ST=Kanagawa/L=Kawasaki/O=GCD/OU=Hiroaki Sengoku/CN=GCD_Sengoku_CA/emailAddress=sengoku@gcd.org
depth0=/C=JP/ST=Kanagawa/L=Kawasaki/O=GCD/OU=Hiroaki Sengoku/CN=test/emailAddress=sengoku@gcd.org

「depth2=」の行が、root CA 「GCD Root CA」の証明書の識別名であり、
「depth1=」の行が、「GCD Root CA」の子CA 「GCD_Sengoku_CA」の証明書の識別名であり、
「depth0=」の行が、「GCD_Sengoku_CA」が発行した証明書の識別名である。
root CA, 子, 孫, 曾孫, ... と、中間CA の階層数が増えて証明書の連鎖が続く場合、 「depth」の数字は大きくなり、 頂点である root CA の「depth」の数字が最も大きくなる。 一方、 サーバないしクライアント自身の証明書は常に「depth0」である。 stone では、depth0 から depth9 まで、扱うことができる。

各識別子 「depth0」~「depth9」が満たすべき正規表現を、
それぞれ 「-z re0=正規表現」~「-z re9=正規表現」(クライアント認証の場合)
あるいは 「-q re0=正規表現」~「-q re9=正規表現」(サーバ認証の場合) で指定する。
stone の設定ファイルの一例を次に示す:

-z key=key.pem
-z cert=cert.pem
-z verify
-z CApath=/usr/local/ssl/certs
-z re2="/CN=GCD Root CA/"
-z re1="/CN=GCD_Sengoku_CA/"
-z re0="/CN=test[0-9]*/"
localhost:25 465/ssl

指定した全ての「re0=正規表現」~「re9=正規表現」が、 それぞれ証明書連鎖の「depth0」~「depth9」とマッチするときに限り、 stone は接続を許可する。

stone 2.3c (近日公開予定) 以降では、 root CA から数えた階層数で「re数字=正規表現」を指定することもできる。 すなわち、root CA を「-1」とし、root CA の子CA が「-2」、 以下順に数字が減っていく。 したがって、上述の設定は以下の設定と等価である:

-z key=key.pem
-z cert=cert.pem
-z verify
-z CApath=/usr/local/ssl/certs
-z re-1="/CN=GCD Root CA/"
-z re-2="/CN=GCD_Sengoku_CA/"
-z re0="/CN=test[0-9]*/"
localhost:25 465/ssl

CA の階層数が 2 (すなわち root CA が depth2) の場合、 「re2=」と「re-1=」、および「re1=」と「re-2=」は、 それぞれ同じ証明書を示す。 両方指定した場合、後者が優先される。 この指定方法を用いると、 中間CA の階層数が一定でなくても、 root CA あるいはその子CA の識別名が満たすべき正規表現を指定することが 可能である。 例えば、

-z re-1="/CN=GCD Root CA/"
-z re0="/CN=test[0-9]*/"

などと指定すると、 中間 CA の階層数にかかわらず、 root CA が「GCD Root CA」であり、 CN が「test[0-9]*」にマッチするような証明書を許可する。

(次回に続く)

Filed under: stone 開発日記 — hiroaki_sengoku @ 08:37
2006年8月13日

stone の SSL 認証 (2)

前回説明したように、 stone で SSL クライアント認証を行なうには、

-z verify -z CApath=ディレクトリ

という設定で実行すればよい。

クライアント認証の代わりにサーバ認証を行ないたい場合、 すなわちサーバが提示した証明書を検証して、 通信相手であるサーバが正に意図した通りの相手であるか確認したい場合は、 「-z」を「-q」に置き換える。 例えば、 ローカルホストの 12345番ポートへの接続を https://www.klab.org へ 中継しつつサーバ認証を行なう場合、 次のように実行する:

% stone -q verbose -q verify -q CApath=/usr/local/ssl/certs www.klab.org:443/ssl 12345
Aug 13 16:13:35.576345 16384 start (2.3c) [3925]
Aug 13 16:13:35.588527 16384 stone 3: m139.tdc.klab.org:https/ssl <- 0.0.0.0:12345
Aug 13 16:13:41.364823 16384 3 TCP 6: [depth2=/L=ValiCert Validation Network/O=ValiCert, Inc./OU=ValiCert Class 2 Policy Validation Authority/CN=http://www.valicert.com//emailAddress=info@valicert.com]
Aug 13 16:13:41.383216 16384 3 TCP 6: [depth1=/C=US/ST=Arizona/L=Scottsdale/O=Starfield Technologies, Inc./OU=http://www.starfieldtech.com/repository/CN=Starfield Secure Certification Authority/emailAddress=practices@starfieldtech.com]
Aug 13 16:13:41.383495 16384 3 TCP 6: [depth0=/O=*.klab.org/OU=Domain Control Validated/CN=*.klab.org]
Aug 13 16:13:41.465200 16384 [SSL cipher=AES256-SHA]
Aug 13 16:13:41.482583 16384 [SSL serial=3db9d6]
Aug 13 16:13:41.482633 16384 [SSL subject=/O=*.klab.org/OU=Domain Control Validated/CN=*.klab.org]
Aug 13 16:13:41.482658 16384 [SSL issuer=/C=US/ST=Arizona/L=Scottsdale/O=Starfield Technologies, Inc./OU=http://www.starfieldtech.com/repository/CN=Starfield Secure Certification Authority/emailAddress=practices@starfieldtech.com]

この実行例では、 stone を実行した後、ローカルホストの 12345番ポートへ (telnet 等のプログラムを使って) 接続を行なっている。 すると、stone は www.klab.org の 443番ポートへ接続する。 www.klab.org が提示した証明書を受けて、 stone はその証明書を発行した root CA を調べる。

上記実行例 4行目「depth2=...」 に表示されているように、 root CA は「ValiCert Class 2 Policy Validation Authority」である。 この root CA の証明書 (ハッシュ値は bcdd5959) は、 「/usr/local/ssl/certs」ディレクトリに 「bcdd5959.0」というファイル名で用意してあったので、 stone はサーバ認証を行なうことができている。

Firefox などの WWWブラウザは、 メジャーな root CA の証明書を最初から全て持っている。 したがって https な URL を開くとき、 自動的にサーバ認証を行なうことができる。 stone の場合はデフォルトの状態では何も root CA 証明書を持っていないので、 サーバ認証/クライアント認証を行なおうとする場合は、 root CA 証明書を置くディレクトリを用意し、 CApath で指定する必要がある。

なお、WWW ブラウザで普通に「サーバ認証」を行なう場合は、 単にサーバが提示する証明書が既知の root CA から発行されているだけでは 充分ではなく、 その証明書の識別名において、 「CN=...」が示す FQDN がサーバのホスト名と一致しなければならない。
stone でも「CN=...」が示す FQDN をチェックすることができるが、 その方法については 次回説明する

上記実行例 6行目「depth0=...」が、 サーバが提示したサーバ証明書である。
なお、「CN=*.klab.org」 と表示されていることから分かるように、 このサーバ証明書はワイルドカード証明書であり、 klab.org ドメインの任意のホストの身元証明に使用できる。

さて、これで stone を使って通信相手がサーバの時もクライアントの時も、 通信相手を SSL 認証することができるわけであるが、 逆に認証してもらう側はどのような設定を行なえばよいのだろうか?

実は、stone がサーバ認証を受ける側であるときの設定方法は、 すでに前回説明している。 サーバ証明書の秘密鍵および公開鍵が、 それぞれ「key.pem」と「cert.pem」というファイル名で 保存してあるなら、次のように stone を実行する。

stone -z key=key.pem -z cert=cert.pem localhost:80 443/ssl

stone がクライアント認証を受ける側であるときの設定方法は、 「-z」を「-q」に置き換えるだけである。 すなわち、

stone -q key=key.pem -q cert=cert.pem warp.klab.org:443/ssl 12345

このように実行しておいて、 ローカルホストの 12345番ポートへ接続すれば、 クライアント認証を要求する https サーバ「warp.klab.org」へ 接続することができる(もちろん、key.pem および cert.pem が適切であれば)。

(次回に続く)

Filed under: stone 開発日記 — hiroaki_sengoku @ 16:19
2006年8月12日

stone の SSL 認証 (1)

stone の SSL 暗号化/復号の機能は、 おかげさまで沢山の方々に使って頂いている。 任意の TCP/IP 接続を手軽に SSL 化できるので、 SSL 化のためだけに stone を使っている、というかたも多そうだ。

例えば 25番ポートで接続を受付ける SMTP サーバがある場合、

stone -z key=key.pem -z cert=cert.pem localhost:25 465/ssl

などと実行するだけで、 SSL 化した SMTP を 465番ポートで受付けることができるようになる。 ここで、「key.pem」および「cert.pem」は、 それぞれ秘密鍵と公開鍵のファイルであり、 OpenSSL のコマンドを使って作ることができる (日経Linux に以前私が書いた連載記事の 「第 6 回 stone (後編) 公開かぎと秘密かぎ」でも作り方を説明した)。

SSL (Secure Socket Layer) というと 暗号化を思い浮かべる人が多いかも知れないが、 暗号化に負けず劣らず重要なのが、通信相手の認証である。 認証、つまり通信相手の確認をせずに通信内容だけ暗号化しても、 通信相手が意図した相手でなく盗聴者だったりしたら、 少しも「セキュア」ではない。

もちろん stone には通信相手を SSL で認証する機能がある。 クライアント認証、すなわち通信相手がクライアントの場合は、 「-z verify」を指定し、 サーバ認証、すなわち通信相手がサーバの場合は、
-q verify」を指定する。

ちなみに stone の SSL 関連のオプションは 全て「-z」か「-q」が前につく。 「-z」は stone がサーバとして振る舞うときのオプション、 「-q」は stone がクライアントとして振る舞うときのオプションである。 通信相手がクライアントの場合というのはつまり stone がサーバとして振る舞う わけだから、「-z verify」になる。

前述した「SSL 化した SMTP」を受付ける stone の場合であれば、 通信相手はクライアントであるから、 「-z verify」を指定する。 このオプションを指定すると、 stone はクライアントに証明書の提示を要求する。 そして提示された証明書を検証するためには、 その証明書を発行した root CA の証明書が必要である。

もちろん、「証明書の提示」といっても、 証明書を送信すれば済む、というものではない。 証明書自体は公開情報 (公開鍵) なので、 それを送信してもクライアントの身の証にはならない。 証明書には対応する秘密鍵があって、 この秘密鍵を持っていることこそがクライアントの身の証である。 SSL プロトコルによってクライアントは秘密鍵をサーバに送信することなく、 自身が秘密鍵を有することをサーバに納得させることができる。

そして、クライアントの身元を第三者的に証明するのが CA の役割である。 ここで言う CA は、 Certificate Authority (認証局) のことであって、 もちろん Cabin Attendant のことではない。 CA を認証する親 CA といった形で CA の階層構造が作られ、 クライアントの証明書を発行するのは末端の CA であることもあるが、 stone が持っている必要があるのは階層構造の一番上、 root CA の証明書のみである (root つまり「根っこ」)。 root CA の証明書一つで、 root CA の子 CA, 孫 CA, ... 階層構造に含まれる全ての CA が発行した 証明書の検証を行なうことができる。

root CA の証明書を置くディレクトリは、 「-z CApath=ディレクトリ」で指定する。 例えば「/usr/local/ssl/certs」ディレクトリに root CA の証明書を置く場合は、
-z CApath=/usr/local/ssl/certs」と指定する。

stone がクライアントに証明書の提示を要求し、 クライアントがそれに答えた場合、 stone はクライアントが提示した証明書を発行した root CA を調べ、 その識別名の「ハッシュ値」を求める。 root CA の識別名のハッシュ値は、 root CA の証明書のファイル名を「CA-cert.pem」とすると、 次のように「openssl x509」コマンドで求めることができる。

% openssl x509 -hash -noout -in CA-cert.pem
ee31d843

次に stone は、 「-z CApath=ディレクトリ」で指定された証明書ディレクトリ (この例では「/usr/local/ssl/certs」) の中の、 「ハッシュ値.数字」というファイル名の証明書を用いて、 クライアントが提示した証明書の検証を行なう。

例えば root CA の識別名のハッシュ値が「ee31d843」であり、 「/usr/local/ssl/certs/ee31d843.0」が存在するなら、 このファイルを root CA の証明書として用いる。 なぜ ファイル名に「.0」という拡張子をつけるかというと、 この証明書ディレクトリに複数の root CA 証明書を置きたい場合、 「たまたま」ハッシュ値が一致してしまう場合があり得るからである。 その場合は、どちらか一方の証明書の拡張子を「.1」にする。

root CA 証明書は、 「ハッシュ値.数字」という形式のファイル名でなければ stone は参照しないが、 かといって「ハッシュ値.数字」というファイル名では、 どのファイルがどの root CA の証明書だか分からなくなってしまうので、 次のように root CA 証明書「CA-cert.pem」のシンボリックリンクを 作成しておくとよいだろう。

% ln -s CA-cert.pem /usr/local/ssl/certs/ee31d843.0

Windows など、シンボリックリンクが存在しない OS の場合は、 単にコピーしておくだけでもよい。

C:\usr\local\ssl\certs> copy CA-cert.pem ee31d843.0

以上、まとめると stone の設定は次のようになる:

-z key=key.pem
-z cert=cert.pem
-z verify
-z CApath=/usr/local/ssl/certs
localhost:25 465/ssl

この設定を例えば stone.config というファイル名で保存し、 「stone -C stone.config」などと実行すれば、 正しいクライアント証明書を提示したクライアントからの接続のみ受付け、 証明書を提示しないクライアントや、 証明書ディレクトリに証明書が存在しない root CA が発行したクライアント証明書を 提示したクライアントからの接続を拒否する。

クライアントが、既知の root CA (の子 CA) が発行した証明書を提示した場合の例:

# stone -z verbose -C stone.config
Aug 12 08:04:13.769309 16384 start (2.3c) [21093]
Aug 12 08:04:13.773038 16384 stone 3: 127.0.0.1:smtp <- 0.0.0.0:465/ssl
Aug 12 08:04:26.534690 16384 3 TCP 5: [depth2=/C=JP/ST=Kanagawa/L=Kawasaki/O=GCD/CN=GCD Root CA/emailAddress=root@gcd.org]
Aug 12 08:04:26.551203 16384 3 TCP 5: [depth1=/C=JP/ST=Kanagawa/L=Kawasaki/O=GCD/OU=Hiroaki Sengoku/CN=GCD_Sengoku_CA/emailAddress=sengoku@gcd.org]
Aug 12 08:04:26.551674 16384 3 TCP 5: [depth0=/C=JP/ST=Kanagawa/L=Kawasaki/O=GCD/OU=Hiroaki Sengoku/CN=test/emailAddress=sengoku@gcd.org]
Aug 12 08:04:26.573916 16384 [SSL cipher=AES256-SHA]
Aug 12 08:04:26.573974 16384 [SSL serial=13]
Aug 12 08:04:26.574000 16384 [SSL subject=/C=JP/ST=Kanagawa/L=Kawasaki/O=GCD/OU=Hiroaki Sengoku/CN=test/emailAddress=sengoku@gcd.org]
Aug 12 08:04:26.574022 16384 [SSL issuer=/C=JP/ST=Kanagawa/L=Kawasaki/O=GCD/OU=Hiroaki Sengoku/CN=GCD_Sengoku_CA/emailAddress=sengoku@gcd.org]

1行目のコマンドラインで「-z verbose」と指定しているが、 これは SSL 関係の詳細ログを表示させるためのオプションである。 「depth2=...」(4行目) が、 root CA の証明書の識別名である。 この証明書は stone にとって既知の証明書なので、 検証が成功する。 「depth1=...」(5行目) は、 root CA の子 CA の証明書の識別名であり、 「depth0=...」(6行目) は、 クライアントの証明書である。

クライアントが、未知の CA が発行したクライアント証明書を提示した場合の例:

Aug 12 08:05:15.587108 16384 3 TCP 5: [depth1=/C=JP/ST=Tokyo/L=Minato-ku/O=K Laboratory Co.,Ltd/CN=KLAB Root CA/emailAddress=root@klab.org]
Aug 12 08:05:15.587172 16384 3 TCP 5: verify error err=19 self signed certificate in certificate chain
Aug 12 08:05:15.587415 16384 3 TCP 5: SSL_accept lib error:140890B2:SSL routines:SSL3_GET_CLIENT_CERTIFICATE:no certificate returned

1行目に、別の root CA の識別名を表示しているが、 この root CA は証明書ディレクトリの中に証明書が存在しないので、 この stone にとって未知である。 したがって、2行目 「verify error err=19 self signed certificate in certificate chain」 つまり「クライアントが自分で署名した証明書 (いわゆる「オレオレ証明書」) を 提示した」と、 検証に失敗した旨のエラーメッセージが出力されている。 クライアントからの接続はこの直後に切断される。

(次回に続く)

Filed under: stone 開発日記 — hiroaki_sengoku @ 08:45
2006年7月14日

stone 2.3b のバグ

重大なバグが発見されない限り正式リリースとする予定だった stone 2.3b であるが、 あいにくバグが発見されてしまった。 SSL 接続が確立する前に TCP 接続が切れてしまうと、 まれに busy loop が発生する。 某環境では、health check と称して ;-( ごく短い時間でポートのオープン/クローズを繰り返しているので、 何日か走らせると load average が無駄に 1 増えてしまっていた。 逆に言うと、かなり無茶な使い方をしない限り、この現象は起きないと思われる。

なぜ busy loop が起きるかと言えば、 doReadWrite() select loop において、 close 予定のソケットについて書込み待ち FD_SET して select(2) を行なうケースがあったからだ。 close 予定のソケットだから、書込み可能でも何も書込まない。 だから select(2) は待ち時間 0 で常に書込み可能を返す。 これが延々繰り返される。

この現象を回避するには、 close 予定のソケットについては FD_SET しなければよい。 ただし SSL_shutdown(3) が書込み待ちでブロックすることがあるので、 この場合、すなわち sf_sb_on_w (SSL flag: shutdown blocked on write) が セットされているときは、 close 予定であっても FD_SET する。 stone.c 2.2.3.6 で修正済み。 なお、busy loop は select 版でのみ確認したが、 epoll 版でも発生する可能性があると思われる。 今回の修正で epoll 版でも busy loop を防ぐことができると思われる。

ついでに、busy loop が起きたときにそれを検知するコードも追加した。 doReadWrite() select loop において busy loop が発生すると、 「<sd> TCP <sd>: doReadWrite Can't happen spin occured」 というエラーログを出力する。 stone が出力するログで「Can't happen」から始まるものは、 想定外の事象が起きたことを示す。 つまりバグである。 stone の最新版において、このようなログに遭遇したかたは、 ご連絡頂けると大変ありがたい。

Filed under: stone 開発日記 — hiroaki_sengoku @ 12:04
2006年6月17日

stone 2.3b リリース

stone 2.3b をリリースした。 現時点ではスナップショット扱いであるが、 重大なバグが発見されない限り、 このバージョンを stone 2.3 に代えて正式リリースとする予定。 stone 2.3a からの変更点は以下の通り。

doReadWrite select ループを抜けるバグを修正

stone.c 2.2.2.6 で doReadWrite から doReadWritePair を分離したときに作りこんでしまったバグを修正。

このバグにより、意図せず doReadWrite select ループを抜けてしまい、 パフォーマンスが大幅に低下するケースがあった。 詳しくは SSL_Pending を参照。

アーカイブから stone.1 と stone.1.ja を削除

stone 2.3 と stone 2.3b との仕様の差分を、 マニュアル stone.1 および stone.1.ja に反映できていないので、 とりあえず削除。

現時点での stone の正式マニュアルは、 パッケージ同梱の README.txt および README.en.txt である。

変数名の変更など

関数の仮引数と同じ変数名のローカル変数を使っていた。 stone.c 2.2.2.23 で修正。

Filed under: stone 開発日記 — hiroaki_sengoku @ 22:02
2006年6月2日

SSL_pending

SSL_pending をマニュアルで調べると、

SSL_pending - obtain number of readable bytes buffered in an SSL object

SSL_pending() returns the number of bytes which are available inside ssl for immediate read.

と書いてある。つまり OpenSSL ライブラリ内に受信可能データがある場合、 そのデータバイト数を返す関数である。

なぜこんな関数が必要かというと、 データを送信しようとして SSL_write を呼び出したときも、 TCP/IP レベルでは送信だけでなく、受信も行なわれるからだ。 SSL のような暗号通信の場合、 送信は「垂れ流し」では済まず、ハンドシェークを行なう必要があるからだが、 この時、受信しようと思っていなかったデータ、 つまり通信相手が送信したデータまで読み込んでしまう場合がある。

すると、SSL_write を呼んでいるのに、 OpenSSL の受信バッファに、意図せずデータが溜まってしまう。 こうなってしまうと、select(2) や epoll(2) では検知できない。 select(2) や epoll(2) は、 I/O レベルでの受信データの有無を調べるシステムコールであり、 それより上のレベルである OpenSSL ライブラリの受信バッファのことは 関知しないからだ。

stone では、SSL_write で全てのデータを送り終わったとき、 SSL_pending を呼び出して受信すべきデータがないか確認している。 これを行なっているのが、 中継元/中継先との送受信を行なう stone の中核関数 doReadWritePair である。

select 版 stone の場合、 doReadWritePair を呼び出すのは doReadWrite である。 この関数は、 中継元/中継先との通信を行なうソケットディスクリプタのみを監視する select ループである。 epoll 版と異なり、select 版では、 select で監視するソケットの数が増えるとパフォーマンスが落ちるので、 送受信が継続しているときはメインの select ループとは別のスレッドを作成して、 パフォーマンスの低下を回避している。 送受信が途絶える (0.1 秒以上送受信が行なわれない) と、 この doReadWrite select ループを抜け、スレッドを終了して、 メイン select ループでソケットを監視する状態に戻る。

stone 2.3a (正確に言うと、stone.c 2.2.2.6 ~ 2.2.2.23) では、 SSL_pending で受信すべきデータを検知したとき、 SSL_read を行なってデータを受信した直後に、 この doReadWrite select ループを抜けてしまっていた。 本来なら、データを受信したのだから 直ちにそれを中継するために送信しなければならないのであるが、 ループを抜けてしまったために、いったんスレッドを終了し、 メイン select ループで送信可能か確認し、 再びスレッドを生成して doReadWrite select ループに入る、 という無駄が生じてしまっていた。

したがって、SSL_pending でデータが検知されることが多発するような場合は、 転送速度が著しく遅くなる。 例えば、stone の受信バッファを意図的に小さくすることによって OpenSSL 内のバッファに受信データが残りやすくして SSL 通信を受信してみる:

-X 512
-z key=key.pem
-z cert=cert.pem
localhost:22 localhost:12346/ssl --

「-X 512」オプションによって、受信バッファのサイズを 512 バイト (デフォルトは 2048 バイト) にしている。 12346番ポートで SSL 接続を受付け、 それを復号した上で、22番ポートへ中継する設定である。 続いて、

localhost:12346/ssl localhost:12347 --

という設定で stone を走らせて、12347番ポートで受付けた接続を、 SSL で暗号化した上で 12346番ポートへ転送するようにする。 上記二つの stone を実行することによって、 12347番ポートへ接続すると、 後者の stone によっていったん SSL で暗号化された上で、 前者の stone で復号されて 22番ポートへつながる。 つまり 12347番ポートで ssh 通信が行なえる。

前者の stone として stone 2.3a select 版を使うと、 ssh 転送が異様に遅くなる。

% scp -P 12347 /boot/linuz-2.6.16.18 localhost:/tmp/
...
linuz-2.6.16.18   100% 1211KB  11.3KB/s  01:47

延々 2 分近くかかってしまうが、 最新の stone.c 2.2.2.25 を使って select 版を作ると、 1 秒以内に転送が終わる。 また、stone 2.3a select 版であっても「-X 512」オプションを指定しなければ、 SSL_pending でデータが検知されるケースがほとんどなくなるので、 1 秒以内に転送が終わる。 なお、stone 2.3a epoll 版には、doReadWrite ループがなく、 全てメインループで処理を行なうので、 このような問題はない。

Filed under: stone 開発日記 — hiroaki_sengoku @ 18:51
2006年5月6日

stone 2.3a リリース

stone 2.3a をリリースした。 stone 2.3 からの変更点は以下の通り。

二重 free するバグの修正

stone 2.3 における最も重大なバグ。 同時に大量に socket を close する、という利用パターンにて、 二つのスレッドが同時に同じ ExBuf に対して ungetExBuf を行なってしまうために、 二重 free が起きてしまう。 stone.c 2.2.1.11 で修正 (わずか一行の修正だが極めて重要)。

Pair を thread で処理しているときは scanClose 対象から外す。

thread 処理中は、そもそも close 要求が出ていないわけで、 当然 scanClose 対象から外れるはず、と思っていたが、 thread 処理中に proto_close をセットすることもあるわけで、 dowrite が ungetExBuf を呼ぶタイミングと、 scanClose が freePair を呼び出して ungetExBuf を呼ぶタイミングとが 重なる可能性があると思われるので、その防止策。

socket を close しないバグの修正

SSL_accept に失敗したとき、すなわち TCP レベルでの accept は成功したが、 続く SSL ハンドシェークで失敗した時に、 中継先の socket が open しっぱなしになるバグ。 これは、中継元 socket の accept に成功した時点で、 中継先の socket を open するが、 SSL_accept に失敗したときに close していなかったために起きた問題。 stone.c 2.2.1.8 で修正 (これもわずか一行の修正だが重要)。

SSL ハンドシェークを行なわない TCP レベルのヘルスチェックを stone の中継元ポートに対して行なったとき、 stone が open した socket がどんどん増え、 too many open files エラーが起きて accept 出来なくなったことにより発覚。

SSL_accept に失敗したとき、pair2 側に close 要求出すのを忘れていた。 SSL_accept に時間がかからない時 (localhost からの接続時) は、doaccept 内で freePair するので問題にならないが、 SSL_accept に時間がかかる時は、doReadWrite 内で p[i]->proto |= proto_close; するので、この時 p[1-i]->proto |= proto_close; も行なう必要がある。

epoll 対応 (select 版も大幅に性能向上)

select(2) の代わりに epoll(2) も利用できるようにした。 コンパイルオプション「-DUSE_EPOLL」をつけることにより epoll 版を make できる。 linux 2.6 以上のカーネルと、 glibc 2.3 以上が必要。

epoll 対応のため、stone のコード全体の見直しを行なった。 結果、(epoll を使わない) select 版も大幅にパフォーマンスが向上した。 「stone 2.3 が遅い理由」および 「stone 2.3a (候補) ベンチマーク」を参照。

アドレスキャッシュ機能の実装

Windows 2000 などで、gethostbyname が遅いという問題があるようなので、 proxy で resolv した IP アドレスをキャッシュする。 stone.c 2.2.2.11 で実装。

unix domain socket で uid 等の取得

SO_PEERCRED 参照。

  <host>:<port>/http <sport> <request> [<xhost>...]

http リクエストにのせて中継します。<request> は、 HTTP 1.0 で規定されるリクエストです。 リクエスト文字列中、 「\」はエスケープ文字であり、 次のような置き換えが行なわれます。

  \u   接続元のユーザID (番号)
  \U   接続元のユーザ名
  \g   接続元のグループID (番号)
  \G   接続元のグループ名

stone.c 2.2.2.18 で実装。

ミリ秒単位のログ出力

stone.c 2.2.2.12 および stone.c 2.2.2.17 で実装。

health HELO コマンドの分割

stone.c 2.2.2.13 で実装。

Filed under: stone 開発日記 — hiroaki_sengoku @ 20:54
2006年5月1日

EPOLL_CTL_ADD を行うタイミング

epoll版 stone で EPOLL_CTL_ADD を行うタイミングについて。

基本的には doAcceptConnect 内で行う。 つまり、中継元からの接続を accept し、 中継先へ connect するルーチン。 このルーチンは、Pair リストへの挿入 (insertPairs) を行う 唯一のルーチンでもある。 このルーチンで EPOLL_CTL_ADD を行わないのは、以下の場合のみ。

(1) proto_close フラグが立っている場合
エラー等の原因で close 要求が行われている場合、 中継の必要がないから、EPOLL_CTL_ADD も行わない。
(2) 中継先の Pair かつ proto_noconnect フラグが立っている場合
proto_noconnect フラグが立っているのは 中継を行わない Pair であることを示す。
この場合は、EPOLL_CTL_ADD を行う必要はない。

ところが、stone を proxy として用いるとき、 proto_noconnect フラグが立つ。 これは中継元からリクエストヘッダを受け取るまでは 中継先が決まらないため。 このとき、(2) に該当するので EPOLL_CTL_ADD が行われない。

このため、stone.c 2.2.2.20 では、proxy として用いたとき EPOLL_CTL_ADD が行われず、 中継先への connect 完了を検知できなくなってしまっていた。

中継先が決まった段階で EPOLL_CTL_ADD を行うよう修正。

$Id: stone.c,v 2.2.2.21 2006/04/29 07:32:28 hiroaki_sengoku Exp $

Filed under: stone 開発日記 — hiroaki_sengoku @ 18:24
Older Posts »