<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>仙石浩明の日記 &#187; stone 開発日記</title>
	<atom:link href="http://www.gcd.org/blog/category/stone/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.gcd.org/blog</link>
	<description>CTO兼プログラマ兼システム管理者の視点から</description>
	<lastBuildDate>Wed, 01 Feb 2012 00:44:57 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.1.2</generator>
		<item>
		<title>Android 端末上で透過型プロキシを動かしてみる 〜 VPN のように使えて、しかも省電力</title>
		<link>http://www.gcd.org/blog/2011/06/806/</link>
		<comments>http://www.gcd.org/blog/2011/06/806/#comments</comments>
		<pubDate>Thu, 02 Jun 2011 00:41:20 +0000</pubDate>
		<dc:creator>hiroaki_sengoku</dc:creator>
				<category><![CDATA[Android]]></category>
		<category><![CDATA[stone 開発日記]]></category>

		<guid isPermaLink="false">http://www.gcd.org/blog/?p=806</guid>
		<description><![CDATA[透過型プロキシ (Transparent Proxy) というのは、 ブラウザから 「見えない」 プロキシのこと。 ブラウザ自身は WWW サーバにアクセスしているつもりなのに、 ブラウザが送信したリクエストをプロキシが [...]]]></description>
			<content:encoded><![CDATA[<p>
透過型プロキシ (Transparent Proxy) というのは、
ブラウザから 「見えない」 プロキシのこと。
ブラウザ自身は WWW サーバにアクセスしているつもりなのに、
ブラウザが送信したリクエストをプロキシが横取りし、
プロキシから出し直す。
サーバからのレスポンスは当然プロキシに返り、
プロキシがそれをブラウザに送信するのだけど、
パケットがブラウザに届くまでの間に送信元アドレスが書き換えられて、
サーバから直接レスポンスが届いたようにブラウザからは見える。
</p>
<p>
フツーの 「見える」 プロキシは、
ブラウザ等でプロキシ設定が必要であるのに対し、
透過型プロキシだと設定が不要。
だから一部の ISP (インターネット接続プロバイダ) などで、
フツーのプロキシの代りに使われていたりする
(ユーザにプロキシ設定の方法を説明する必要がなくてサポートコストが削減できる)。
あるいは企業等で、
従業員が仕事と関係ない Web ページを閲覧していないか監視するために、
社内から社外へ接続するゲートウェイ等に透過型プロキシを設置して、
社外への http アクセスを記録していたりする
(監視だけならパケットをダンプするだけでも用が足りるが、
アクセス先のサイト毎に細かな制御を行なおうとすると、
プロキシを使った方が楽)。
</p>
<p>
このように、
透過型プロキシはその存在を隠すために使われることが多いが、
ブラウザにプロキシ設定の機能が無い場合は、
透過型プロキシを使わざるを得ない。
例えば、
Android 端末で使われるブラウザの多くが、
なぜかプロキシ設定の機能を持っていない。
プロキシ経由でアクセスされると、
モバイル端末からのアクセスなのか、
フツーの PC からのアクセスなのか、
区別がつかなくなってしまうので、
あえてプロキシ設定できないようにしているのかも知れないが、
プロキシを使わないとアクセスできない場合は困ってしまう。
</p>
<p>
私の場合、
勤務先の LAN 内のサーバに社外からアクセスするとき、
社内アクセス専用のプロキシ 
(ここでは仮にホスト名を proxy.klab.org とする) を利用している。
例えば <a href="http://senri.gcd.org/test">senri.gcd.org</a> 
(自宅のサーバ、つまり社外) からこのプロキシをアクセスすると、
こんな感じ:
</p>
<pre class="terminal-scroll">
senri:~ $ openssl s_client -connect proxy.klab.org:443 -cert cert.pem -key key.pem -CApath /usr/ssl/certs -quiet<span style="color:red">↓</span>
Enter pass phrase for key.pem:xxxxxxxx<span style="color:red">↓</span>

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<span style="color:red">↓</span>
<span style="color:red">↓</span>
HTTP/1.0 200 OK

:irc.klab.org NOTICE AUTH :*** Looking up your hostname...
:irc.klab.org NOTICE AUTH :*** Found your hostname
</pre>
<p>
行末に 
「<span style="color:red">↓</span>」
がある行が入力行。
「<span style="color:red">↓</span>」
は Enter キー押下。
秘密鍵 key.pem でクライアント認証を受けて proxy.klab.org
に接続し、
CONNECT リクエストを送信することにより、
社内の任意のサーバ:ポートに接続できる。
上記の例では<a href="http://www.gcd.org/blog/2006/12/382/">社内 IRC サーバ</a>である 
irc.klab.org:6667 に接続しているが、
もちろん社内の任意の WWW サーバにも接続できる。
</p>
<blockquote>
説明を簡単にするため proxy.klab.org と書いたが、
実際には <a href="http://www.gcd.org/blog/2006/05/359/">VPN Warp</a> を使っている
(だから proxy.klab.org というサーバは存在しない)。
VPN Warp も stone で接続することができるので、
以下の説明は VPN Warp の場合もほとんど同様に適用することができる。
</blockquote>
<p>
プロキシというと、
ファイアウォールの内側から外部のインターネットをアクセスするために用いるものと思っている人が多いせいか、
外からファイアウォールの内側をアクセスするためのプロキシは、
特にリバースプロキシ (reverse proxy) と呼ばれることもある。
</p>
<p>
proxy.klab.org を透過型プロキシとして利用できれば、
社内の任意のサーバに透過的にアクセスできるようになる。
例えばこんな感じ:
</p>
<pre class="terminal">
senri:~ $ adb shell<span style="color:red">↓</span>
$ getprop ro.build.description<span style="color:red">↓</span>
soju-user 2.3.4 GRJ22 121341 release-keys
$ su<span style="color:red">↓</span>
# busybox traceroute irc.klab.org<span style="color:red">↓</span>
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  *  <span style="color:red">^C</span>
senri:~ $ adb shell<span style="color:red">↓</span>
$ telnet irc.klab.org 6667<span style="color:red">↓</span>
:irc.klab.org NOTICE AUTH :*** Looking up your hostname...
:irc.klab.org NOTICE AUTH :*** Found your hostname
</pre>
<p>
「<a href="http://en.wikipedia.org/wiki/Soju">soju</a>-user 2.3.4 GRJ22」
つまり Android 2.3.4 な 
<a href="http://www.gcd.org/blog/2011/03/777/">Nexus S</a> で、
パケットが届かないはずの irc.klab.org (10.10.0.18) に対して、
telnet でアクセスできている。
telnet でアクセスできるということは、
もちろん任意の <a href="https://market.android.com/details?id=net.andchat">IRC アプリ</a>で
irc.klab.org にアクセスできるということ。
</p>
<p>
VPN (Virtual Private Network) でも同じことができる!
という声が聞こえてきそうだが、
VPN だと TCP でデータを送受信していないときも、 
VPN セッションを張っているだけでいろんなパケット 
(例えば ARP) が行き交って、
そのたびに電波が飛んで電池を消耗するので、
あまりモバイル向きではないと思う。
</p>
<p>
あるいは、
<a href="https://market.android.com/details?id=org.connectbot">ConnectBot</a> 
などを使って ssh で port forward を行なう方法もあるが、
これまた ssh セッションを張りっぱなしだと電池消耗が心配だし、
かといって IRC を使う前に毎回 ssh 接続を行なうのはメンドクサイ。
また、
通信先/宛先ポートが増えるたび port foward 設定を追加するのもメンドクサイ。
</p>
<p>
その点、
透過型プロキシだと実際にパケットが飛ぶときのみ通信が行なわれるので、
電池消耗をあまり心配せずに TCP セッションを張りっぱなしにできる。
また、
LAN 内の通信先/宛先ポートが増えても設定変更は不要。
モバイルで LAN 内にアクセスする方法として最適だと思う
(便利なのに普及していないのはナゼ?)。
</p>
<span id="more-806"></span>
<p>
前述したように、
透過型プロキシでは、
クライアントが送信した往路パケットの宛先を書き換えてプロキシへ送り込む必要がある。
これは、
Linux (もちろん Android を含む) だと 
iptables コマンドを使えば簡単に実現できる:
</p>
<pre class="terminal">
senri:~ $ adb shell<span style="color:red">↓</span>
$ su<span style="color:red">↓</span>
# iptables -t nat -p tcp -A OUTPUT -j REDIRECT -d 10.10.0.0/24 --to-port 9980<span style="color:red">↓</span>
getsockopt for multiport failed strangely: No such file or directory
getsockopt for multiport failed strangely: No such file or directory
FIX ME! implement getprotobyname() bionic/libc/bionic/stubs.c:378
# 
</pre>
<p>
これで 10.10.0.* (LAN 内の IP アドレス) 宛の 
TCP パケットが宛先を書き換えられて、
外部に送信されることなく端末自身の 9980番ポートに届く。
ただし端末によっては
CONFIG_NF_NAT を有効にしていないカーネルを使っている場合があり
(froyo 以前の端末は大半が該当するかも?)、
上記 iptables コマンドを実行すると
「can't initialize iptables table `nat': Table does not exist (do you need to insmod?)」
などのエラーメッセージが出てしまう。
その場合は、
CONFIG_NF_NAT を有効にしてカーネルを再構築すればよい
(Linux カーネルは GPL なので再構築に必要なソースは入手できるはず)。
</p>
<blockquote>
上記の実行例では
「getsockopt for multiport failed strangely: No such file or directory」
というエラーメッセージが出ているが、
これはカーネルに xt_multiport モジュールが組み込まれていないにもかかわらず、
iptables コマンドには multiport 拡張が組み込まれていて、
iptables コマンド実行時に multiport 拡張のバージョンをチェックしようと
getsockopt で IPT_SO_GET_REVISION_MATCH オプションの値を得ようとするため。
カーネルの xt_find_revision (net/netfilter/x_tables.c) が
xt_multiport モジュールを見つけられず、
ENOENT (No such file or directory) を返すため、
このようなエラーメッセージが表示される。
上記の例では multiport 拡張を使っていないので、
このエラーは無視して構わない。
<br />
また、
「FIX ME! implement getprotobyname()」
というエラーメッセージも出ているが、
これは
Android の libc である bionic において
<a href="http://linux.die.net/man/3/getprotobyname">getprotobyname(3)</a> がまだ実装されておらず、
現時点では常に NULL を返している、
という意味。
iptables コマンドはプロトコル名 (例えば 「tcp」) 
からプロトコル番号 (tcp の場合なら 6) への変換テーブルを自前で持っているので、
getprotobyname(3) が NULL を返しても問題無い。
</blockquote>
<p>
あとは 9980番ポートに届いたパケットを、
proxy.klab.org へ転送してやればよい。
ただし、
社内アクセス専用プロキシ
proxy.klab.org を利用するには、
前述したように
(1) SSL クライアント認証を受けることが必要。
さらに、
クライアント (上記の例だと telnet コマンドや IRC アプリ)
がどこへ接続しようとしていたかを調べ、
proxy.klab.org へ
(2) CONNECT メソッドを使って接続先を伝える必要がある
(上記の例だと、
「CONNECT irc.klab.org:6667 HTTP/1.1」
を送信)。
</p>
<p>
まず (1) は<a href="http://www.gcd.org/sengoku/stone/Welcome.ja.html">拙作 stone</a> 
を使うと簡単に実現できる:
</p>
<pre class="terminal-scroll">
senri:~ $ adb shell<span style="color:red">↓</span>
$ cd /data/local/tmp<span style="color:red">↓</span>
$ stone -l -q cert=cert.pem -q key=key.pem -q passfile=key.passwd proxy.klab.org:443/ssl localhost:8080 &amp;<span style="color:red">↓</span>
$ telnet localhost 8080<span style="color:red">↓</span>
CONNECT irc.klab.org:6667 HTTP/1.1<span style="color:red">↓</span>
<span style="color:red">↓</span>
HTTP/1.0 200 OK

:irc.klab.org NOTICE AUTH :*** Looking up your hostname...
:irc.klab.org NOTICE AUTH :*** Found your hostname
</pre>
<p>
クライアントから localhost:8080 に接続すると、
stone が SSL クライアント認証を受けて暗号化を行なった上で
proxy.klab.org:443 へ転送してくれる。
つまりクライアントから見ると、
localhost:8080 に認証不要なプロキシがあるように見える。
</p>
<p>
「-l」 オプションは、
stone のログ出力を Android の logging system へ出力するための指定
(指定しないと stone は標準出力にログを出す)。
logging system へ出力されたログは、
<a href="http://developer.android.com/guide/developing/tools/logcat.html">logcat</a> 
コマンドや<a href="https://market.android.com/details?id=org.jtb.alogcat">アプリ</a>で見ることができる。
</p>
<p>
SSL クライアント認証のための鍵の指定
(「-q cert=」「-q key=」「-q passfile=」オプション)
は、
前述した openssl の指定方法とほとんど同じだが、
stone の場合は Android の認証情報ストレージ (Credential storage)
を利用することもできる (後述)。
</p>
<blockquote>
Android 版 stone は、
「<a href="http://dsas.blog.klab.org/archives/51813490.html">パケットリピータ「stone」を Android へポーティング</a>」
から入手できる。
あるいは <a href="http://sourceforge.jp/cvs/view/stone/stone/">CVS レポジトリ</a>の中の
<a href="http://sourceforge.jp/cvs/view/stone/stone/Android.mk?view=log">Android.mk</a> 
を使って最新版の 
<a href="http://sourceforge.jp/cvs/view/stone/stone/stone.c?view=log">stone.c</a> 
をビルドしてもよい。
</blockquote>
<p>
(2) を実現するには、
クライアントが送信し 9980番ポートに届いてしまったパケットの、
元々の宛先を知る必要がある。
パケットの宛先は既に書き換えられてしまっているので役に立たないが、
Linux では getsockopt を使って 
SO_ORIGINAL_DST オプションの値を調べることにより、
TCP 接続の元々の宛先を得ることができる。
もちろん、
書き換えられた後で外部から届いた TCP 接続の 
「元々の宛先」
なんて分かるわけはなく、
あくまで同一マシンで書き換えられて
(正確に言えば PREROUTING あるいは OUTPUT chain で REDIRECT されて)、
そのままマシンから出ること無く同一マシンに着信した TCP 接続のみ。
</p>
<p>
stone では TCP 接続を転送する際、
TCP セッションの冒頭に
「CONNECT irc.klab.org:6667 HTTP/1.1」
等の文字列 (リクエストヘッダ) を付加することができる。
さらに付加する文字列として、
「CONNECT \D HTTP/1.1」
などと 「\D」 を指定すると、
「元々の宛先」
で置き換えてくれる。
つまり、
以下のように stone を実行することで (2) が実現できる:
</p>
<pre class="terminal-scroll">
senri:~ $ adb shell<span style="color:red">↓</span>
$ su<span style="color:red">↓</span>
# stone -l -q store=sengoku proxy.klab.org:443/ssl,http 9980 'CONNECT \D HTTP/1.1' &amp;<span style="color:red">↓</span>
# exit<span style="color:red">↓</span>
$ telnet irc.klab.org 6667<span style="color:red">↓</span>
:irc.klab.org NOTICE AUTH :*** Looking up your hostname...
:irc.klab.org NOTICE AUTH :*** Found your hostname
</pre>
<p>
転送先の指定 (上記実行例では 「proxy.klab.org:443」) には
「/」 に続けて拡張子をつけることができる。
「ssl」 は SSL で暗号化した上で転送する指定で、
「http」 が
「CONNECT ...」
を proxy.klab.org:443 へ送信するための指定。
「http」
をつけたときは、
転送元 (上記実行例では 「9980」) の次にもう一つ文字列引数が必要になり、
この文字列 ('CONNECT \D HTTP/1.1') をリクエストメソッドとして、
転送先へ送信する。
</p>
<p>
「-q store=sengoku」
オプションを指定することによって stone は
Android の認証情報ストレージ (Credential storage) から秘密鍵等を読み込む。
</p>
<p>
「認証情報ストレージ」
というのは鍵ペア (証明書と秘密鍵) および CA証明書を保存できるストレージで、
実体は /data/misc/keystore にある。
もちろん暗号化されていて、
stone などのプログラムや、
アプリなどから鍵を利用するには、
Android の設定メニューから
「位置情報とセキュリティの設定」 (Location &amp; security) の中の
「安全な認証情報の使用」 (Use secure credentials) 
をチェックしてパスワードを入力する必要がある。
</p>
<p>
stone のオプションで秘密鍵を指定する際、
「-q key=key.pem -q passfile=key.passwd」
などと秘密鍵および秘密鍵のパスワードをファイルで指定するよりは、
「-q store=sengoku」
などと指定 (「sengoku」 は鍵ペアの CN)
して認証情報ストレージを使った方が安全といえる。
</p>
<p>
認証情報ストレージに格納された鍵は、
格納したアプリの権限
(Android ではアプリごとにユーザID が割当てられる)
でないと読み出せないが、
設定メニューの「位置情報とセキュリティの設定 ⇒ USBストレージからのインストール」
で格納すると system (uid=1000) 権限で格納された扱いになる。
しかし system 権限だと <a href="http://linux.die.net/man/3/socket">socket(3)</a> が Permission denied (errno=EACCES) になるので、 
stone の実行権限としては不適当。
</p>
<p>
じゃ、どうすれば? 
と思ってソース frameworks/base/cmds/keystore/keystore.c
を見てみると、
root (uid=0), wifi (1010), vpn (1016) でも 
uid=1000 な鍵を読むことができるらしい。
上記実行例では (安易だが) root で走らせることにした。
</p>
<blockquote>
<a href="http://www.gcd.org/blog/2008/12/165/">WPA EAP-TLS</a> 
な無線LAN AP に接続するときに使用する鍵ペアも、
設定メニューの 「USBストレージからのインストール / Install from USB storage」 
を使って認証情報ストレージに格納しておく。
wpa_supplicant は wifi (uid=1010) 権限で動くので読み出すことができる。<br />
にしても、
「USBストレージ」 とは何ぞ? (なぜ USB が関係あるのか?) と思ってしまうが、
Android 端末によって
「SD カードからインストール / Install from SD card」 だったり
「Install encrypted certificates」 だったりと、
いろいろなバリエーションがあるようだ。
Nexus S の場合、
「SD カード」 が無いので 
「SD カードからインストール」 と書くわけにはいかなかったのだろうが、
普通に 「証明書のインポート」 でいいのではないかと思う。
</blockquote>
<p>
以上で、
Android 端末上で透過型プロキシを動かすことができた。
あとは Android のブート時に、
前述した iptables コマンドと stone を実行するように設定するだけ。
Nexus S や Nexus One および 
Google の設定をそのまま踏襲している Android 端末だと、
/init.rc において 
/system/etc/install-recovery.sh を実行する設定になっているので、
install-recovery.sh に以下のスクリプトを追記すればよい。
</p>
<pre class="code">
iptables -t nat -p tcp -A OUTPUT -j REDIRECT -d 10.10.0.0/24 --to-port 9980
stone -l -n -D -q verbose -q store=sengoku -q verify \
    proxy.klab.org:443/ssl,http 9980 'CONNECT \D HTTP/1.1'
</pre>
<p>
<a href="http://www.cyanogenmod.com/">CyanogenMod</a>
などのカスタムROM だと、
ブート時に
「run-parts /system/etc/init.d」
を実行してくれるので、
/system/etc/init.d ディレクトリの中に上記内容のスクリプトを 
30stone などのファイル名で入れておけば、
ブート時に実行される。
</p>

<p>
6月6日追記:
</p>
<p>
これで OK と思っていたら、
テザリングすると iptables で設定した内容が消えてしまった。
正確に言うと、
テザリングを開始すると nat table の 
POSTROUTING chain に以下のルールが追加され、
</p>
<pre class="terminal">
Chain POSTROUTING (policy ACCEPT 26086 packets, 1638K bytes)
 pkts bytes target     prot opt in     out     source               destination

    0     0 MASQUERADE  0    --  *      rmnet0  0.0.0.0/0            0.0.0.0/0

</pre>
<p>
テザリングを終了すると
filter table の INPUT, OUTPUT, FORWARD の各 chain と、
nat table の全 chain が初期化 (iptables -F) されてしまう。
つまり POSTROUTING chain に追加した上記ルールだけ消せばいいものを、
全 chain の初期化を行なうものだから、
透過型プロキシを実現するために設定した 
nat table の OUTPUT chain まで巻き添えを食って消されてしまう。
</p>
<p>
原因は、
/system/bin/netd において NAT を disable する際
(テザリングを終了させるとき)
setDefaults() で全 table を初期化する実装 (なんて乱暴な!) になっているため。
</p>
<p>
iptables -t nat -A POSTROUTING ... で POSTROUTING chain へ追加したルールは、
きちんと iptables -t nat -D POSTROUTING ... 
で削除すべきだと思うのだが...(*_*)
追加と削除が対にならない事態に陥ることを恐れて、
こういう実装にしているのかも知れないが、
それなら対にならなかったとき (iptables がエラーになったとき) 
のみ初期化すればいいだけの話。
</p>
<p>
以下のような修正で、
初期化ではなく個々のルールを削除させることができる。
</p>
<pre class="terminal">
--- system/netd/NatController.cpp.org	2011-02-28 16:56:08.573705126 +0900
+++ system/netd/NatController.cpp	2011-06-06 18:52:08.730041739 +0900
@@ -92,15 +92,15 @@
     char cmd[255];
 
     // handle decrement to 0 case (do reset to defaults) and erroneous dec below 0
-    if (add == false) {
-        if (natCount &lt;= 1) {
-            int ret = setDefaults();
-            if (ret == 0) {
-                natCount=0;
-            }
-            return ret;
-        }
-    }
+//  if (add == false) {
+//      if (natCount &lt;= 1) {
+//          int ret = setDefaults();
+//          if (ret == 0) {
+//              natCount=0;
+//          }
+//          return ret;
+//      }
+//  }
 
     if (!interfaceExists(intIface) || !interfaceExists (extIface)) {
         LOGE("Invalid interface specified");
@@ -128,8 +128,8 @@
     }
 
     // add this if we are the first added nat
-    if (add &amp;&amp; natCount == 0) {
-        snprintf(cmd, sizeof(cmd), "-t nat -A POSTROUTING -o %s -j MASQUERADE", extIface);
+    if ((add &amp;&amp; natCount == 0) || (!add &amp;&amp; natCount == 1)) {
+        snprintf(cmd, sizeof(cmd), "-t nat -%s POSTROUTING -o %s -j MASQUERADE", (add ? "A" : "D"), extIface);
         if (runIptablesCmd(cmd)) {
             // unwind what's been done, but don't care about success - what more could we do?
             setDefaults();;
</pre>
<p>
ちなみに、
com.android.server.NetworkManagementService の enableNat() と disableNat() が
netd にコマンドを送って NAT の enable/disable を行なっている。
</p>
<p>
/system/bin/ndc を使って同等のコマンドを netd へ送ることが可能:
</p>
<pre class="terminal">
# ndc nat enable usb0 rmnet0<span style="color:red">↓</span>
200 Nat operation succeeded
# iptables -t nat -L POSTROUTING -nv<span style="color:red">↓</span>
getsockopt for multiport failed strangely: No such file or directory
getsockopt for multiport failed strangely: No such file or directory
Chain POSTROUTING (policy ACCEPT 26282 packets, 1650K bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 MASQUERADE  0    --  *      rmnet0  0.0.0.0/0            0.0.0.0/0           
# ndc nat disable usb0 rmnet0<span style="color:red">↓</span>
200 Nat operation succeeded
# iptables -t nat -L POSTROUTING -nv<span style="color:red">↓</span>
getsockopt for multiport failed strangely: No such file or directory
getsockopt for multiport failed strangely: No such file or directory
Chain POSTROUTING (policy ACCEPT 26282 packets, 1650K bytes)
 pkts bytes target     prot opt in     out     source               destination         
# 
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.gcd.org/blog/2011/06/806/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>x86_64 Linux でメモリ・デバッグ・ツール Valgrind を使う場合の注意点</title>
		<link>http://www.gcd.org/blog/2007/11/138/</link>
		<comments>http://www.gcd.org/blog/2007/11/138/#comments</comments>
		<pubDate>Thu, 22 Nov 2007 11:36:13 +0000</pubDate>
		<dc:creator>hiroaki_sengoku</dc:creator>
				<category><![CDATA[stone 開発日記]]></category>
		<category><![CDATA[プログラミングと開発環境]]></category>

		<guid isPermaLink="false">http://www.gcd.org/blog/2007/11/138/</guid>
		<description><![CDATA[次のようなプログラム test.c について考える: #include &#60;stdio.h> #include &#60;stdlib.h> #include &#60;sys/types.h> #include &#038;lt [...]]]></description>
			<content:encoded><![CDATA[<p>
次のようなプログラム test.c について考える:
</p>
<pre class="code">
#include &lt;stdio.h>
#include &lt;stdlib.h>
#include &lt;sys/types.h>
#include &lt;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;
}
</pre>
<p>
malloc(3) で確保した領域のうち、
16 byte を bcopy(3) でコピーするだけの極めて単純なプログラムであり、
特に問題はないように見える。
</p>
<p>
ところが memory debugging tool <a href="http://valgrind.org/">Valgrind</a> を使って検証してみると、
x86_64 Linux だと次のようなエラーが出てしまう。
</p>
<pre class="terminal">
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.
</pre>
<p>
「Invalid read of size 8」、
すなわちアクセスすべきではないメモリを、
64bit (8 byte) 読み込み命令で読んだというエラー。
</p>
<p>
test.c で読み込みを行なう可能性があるところと言えば、
「<tt>bcopy(p->buf, buf, p->len);</tt>」の部分だけであり、
その範囲は printf で表示しているように、
0x4D5C034 番地から 0x4D5C043 番地までの 16 byte である。
</p>
<p>
ところが、Valgrind 曰く:
</p>
<pre class="code">
Address 0x4D5C040 is 16 bytes inside a block of size 20 alloc'd
</pre>
<p>
ちょっと英語の意味が取りにくい (私の英語力が低いだけ? ^^;) が、
つまり「malloc で確保した 20 byte の領域のうち、
先頭から数えて 16 byte 目 (先頭は 0 byte 目と数える) が
0x4D5C040 番地であり、
この番地に対してメモリ読み込みが行なわれた」
という意味である (「16 byte 目」なら
「16 bytes」でなくて「16th byte」のような...?)。
</p>
<p>
すなわち、
「20 byte の領域のうち 16 byte 目」というのは残り 4 byte であり、
あと 4 byte コピーすればいいのにもかかわらず、
64bit 読み込み命令を使って 8 byte いっぺんに読んでしまっているから、
malloc で確保した領域の外をアクセスしてしまう、というわけ。
</p>
<p>
結果として 4 byte 無駄に読んでしまっている
(実はコピー開始位置も 4 byte 前から行なうので、計 8 byte 余計に読み込んでいる)
わけだが、
CPU にとって一番高速にコピーできる単位が
(64bit 境界に合わせた) 64bit 読み書きだから、
bcopy の実装がこのようになっているのだろう。
</p>
<blockquote>
より正確に言えば、
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 境界に一致していない、
というのがミソである。
</blockquote>
<p>
したがって 32bit な x86 Linux の場合であれば 32bit 単位でコピーを行なうので、
test.c ではこのようなエラーは起きない。
もちろん、64bit な x86_64 Linux で Valgrind がエラーを出すからといって、
bcopy の x86_64 における実装に問題がある、というわけではない。
Valgrind は、
あくまでバグの「可能性」を指摘するだけであって、
malloc で確保した領域の外へのアクセスでも、
それが意図的なものであれば (メモリ保護違反などでない限り) 何の問題もない。
</p>
<p>
分かってみれば単純な話なのであるが、
Valgrind のメッセージ「16 bytes inside a block」の意味が把握できなかった私は、
glibc の bcopy のソースを読んで 64bit 単位でコピーを行なっていることを知り、
4 byte の領域外読み込みが行なわれることを理解して初めて、
Valgrind のメッセージの意味が分かったという、
本末転倒な体験をした (^^;)。
</p>
<p>
ちなみに、
もちろん最初から上記のようなテストプログラムを Valgrind で
チェックしようと思ったわけではなく、
「struct test」構造体は実際には次のような SockAddr 構造体であり、
saDup 関数にて malloc した SockAddr 構造体を
doconnect 関数で bcopy する処理になっていて、
元ネタは拙作 <a href="http://www.gcd.org/sengoku/stone/Welcome.ja.html">stone</a> である。
</p>
<pre class="code">
typedef struct {
    socklen_t len;
    struct sockaddr addr;
} SockAddr;
#define SockAddrBaseSize	((int)&amp;((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*)&amp;ss;	/* destination */
...
    bcopy(sa, dst, salen);
...
</pre>
<p>
stone ML にて、
Valgrind で検証したらエラーが出た、という報告を頂いて (_O_)
以上のような調査を行なった次第。
bcopy に与えた引数に問題はなく、
どうしてこれが 「Invalid read of size 8」 エラーを引き起こすのか謎だった。
結果的には stone には問題はなく、
修正の必要もないことが判明したわけであるが、
今まで使っていなかった Valgrind を使ってみるいいきっかけになった。
実を言うと 64bit Linux を (プログラミングのレベルで) 使ったのも、
今回が初めてだったりする (^^;)。
</p>
]]></content:encoded>
			<wfw:commentRss>http://www.gcd.org/blog/2007/11/138/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>stone に Server Name Indication (TLS 拡張) 機能を実装</title>
		<link>http://www.gcd.org/blog/2007/06/122/</link>
		<comments>http://www.gcd.org/blog/2007/06/122/#comments</comments>
		<pubDate>Sun, 24 Jun 2007 22:20:27 +0000</pubDate>
		<dc:creator>hiroaki_sengoku</dc:creator>
				<category><![CDATA[stone 開発日記]]></category>

		<guid isPermaLink="false">http://www.gcd.org/blog/2007/06/122/</guid>
		<description><![CDATA[RFC 3546 で、 TLS (Transport Layer Security つまり SSL) の拡張が規定された。 その中の一つが、「Server Name Indication」と呼ばれる拡張であり、 クライア [...]]]></description>
			<content:encoded><![CDATA[<p>
<a href="http://www.ietf.org/rfc/rfc3546.txt">RFC 3546</a> で、
TLS (Transport Layer Security つまり SSL) の拡張が規定された。
その中の一つが、「Server Name Indication」と呼ばれる拡張であり、
クライアントがサーバに対して、
サーバのホスト名を伝えることができるようになった
(規定されたのは 2003年6月なのであるが、まだあまり普及していない)。
</p>
<p>
なぜクライアントがサーバへ、
当のサーバの名前を伝えてやる必要があるかというと、
サーバが複数のホスト名を持つ場合があるからだ。
例えば WWW サーバは、
http リクエスト中の「Host:」フィールドを見てレスポンスを切り替える。
この機能はバーチャルドメインと呼ばれ、
一つの IP アドレスで複数のホスト名のサービスを提供する方法として、
広く使われている。
</p>
<p>
ところが (従来の) https の場合、この方法が使えない。
WWW サーバは SSL 通信を開始するにあたって、
<font color="red">＊最初に＊</font>サーバ証明書を
クライアントへ送る必要があるからだ。
http リクエスト中の「Host:」フィールドに、
別のホスト名が書いてあったとしても後の祭。
「リクエストしたホスト名と、
サーバから送られてきた証明書に記載されたホスト名が一致しない」という旨の
警告が WWWブラウザに表示されてしまう。
</p>
<p>
もう少し詳しく説明すると、
クライアント (WWWブラウザ) とサーバは、
SSL 通信を始めるにあたって、
次のようなハンドシェークを行なう。
</p>
<table>
<tr>
<th align="center">クライアント</th><th></th>
<th align="center">サーバ</th><th></th><th></th>
</tr><tr>
<td align="right">ClientHello</td><td align="right">→</td><td></td><td></td>
<td>乱数, セッションID, 暗号/圧縮方式</td>
</tr><tr>
<td></td><td>←</td><td>ServerHello</td><td></td>
<td>乱数, セッションID, 暗号/圧縮方式決定</td>
</tr><tr>
<td></td><td>←</td><td>Certificate</td><td></td>
<td><font color="red">サーバ証明書</font></td>
</tr><tr>
<td></td><td>←</td><td>ServerKeyExchange</td><td></td>
<td>共通鍵の交換</td>
</tr><tr>
<td align="right">(</td><td>←</td><td>CertificateRequest</td><td></td>
<td>クライアント認証を要求する時のみ)</td>
</tr><tr>
<td></td><td>←</td><td>ServerHelloDone</td><td></td>
<td>ServerHello の終了を通知</td>
</tr><tr>
<td align="right">(ClientCertificate</td><td align="right">→</td><td></td><td></td>
<td>クライアント認証を要求された時のみ)</td>
</tr><tr>
<td align="right">ClientKeyExchange</td><td align="right">→</td><td></td><td></td>
<td>共通鍵の交換</td>
</tr><tr>
<td align="right">ChangeCipherSpec</td><td align="right">→</td><td></td><td></td>
<td>次のデータから暗号化することを通知</td>
</tr><tr>
<td align="right">Finished</td><td align="right">→</td><td></td><td></td>
<td>以上のハンドシェークのハッシュ値(暗文)</td>
</tr><tr>
<td></td><td>←</td><td>ChangeCipherSpec</td><td></td>
<td>次のデータから暗号化することを通知</td>
</tr><tr>
<td></td><td>←</td><td>Finished</td><td></td>
<td>以上のハンドシェークのハッシュ値(暗文)</td>
</tr>
</table>
<p>
このハンドシェークの後、
クライアントが暗号化された http リクエストを送信し、
それを受けてサーバが暗号化されたレスポンスを返す。
</p>
<p>
https サーバがバーチャルドメイン機能を持つには、
https サーバがサーバ証明書を送信する (上のハンドシェーク図の 3行目) より前に、
クライアントがリクエストしたいホスト名を通知する必要がある。
上図から明らかなように、
ホスト名の通知は一番最初の「ClientHello」で行なわれなければならず、
そのための拡張が、
「Server Name Indication」というわけである。
もちろんこの時点では、まだ鍵の交換は行なわれていないので、
ホスト名は平文で送られる。
</p>
<p>
前置きが長くなってしまったが、
この Server Name Indication (SNI) を stone でサポートしてみた
(<a href="http://cvs.sourceforge.jp/cgi-bin/viewcvs.cgi/stone/stone/stone.c">stone.c</a> Revision 2.3.1.11 以降)。
ただし stone が利用している
<a href="http://www.openssl.org">OpenSSL</a> で SNI がサポートされるのは
0.9.9 以降である (追記: 0.9.8f 以降でもサポートされた) ので、
OpenSSL 0.9.9 以降のライブラリを使って stone を make する必要がある 。
</p>
<p>
二台の http サーバ senri.gcd.org と asao.gcd.org があるとき、
次のように stone を実行する:
</p>
<pre class="code">
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
</pre>
<p>
転送元ポート指定「443/ssl」が二度現われていることに注意。
</p>
<p>
最初の「senri.gcd.org:http 443/ssl」は、
「-z servername=senri.gcd.org」と指定しているように、
クライアントが通知するサーバのホスト名が senri.gcd.org の場合の指定である。
サーバ (つまり stone) は、
「-z cert=ファイル名」と「-z key=ファイル名」で指定されるサーバ証明書を返し、
クライアントからの通信を、
SSL 復号を行なった上で senri.gcd.org:http へ中継する。
</p>
<p>
二番目の「asao.gcd.org:http 443/ssl」についても同様に、
クライアントが asao.gcd.org を通知すれば、
stone は asao.gcd.org のサーバ証明書を返すとともに、
クライアントからの通信を、
SSL 復号を行なった上で asao.gcd.org:http へ中継する。
</p>
<p>
この例では http サーバは二台のみであるが、
同様に何台でも指定できる。
また、もちろん物理的に異なるサーバを用意する必要があるわけではなく、
一台の http サーバで複数のポートを開き、
各ポートで別々のホスト名のサービスを提供してもよい。
</p>
<p>
OpenSSL 0.9.9 の s_client コマンド (SSL クライアント) でアクセスしてみると、
senri.gcd.org を通知すれば (-servername senri.gcd.org オプション)、
</p>
<pre class="terminal">
% 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.
</pre>
<p>
サーバ (stone) は「CN=senri.gcd.org」の証明書を返し、
同じ 443番ポートへのアクセスでも
通知するサーバ名を asao.gcd.org へ変えるだけで、
</p>
<pre class="terminal">
% 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.
</pre>
<p>
サーバが返す証明書が「CN=asao.gcd.org」に変わるし、
stone が中継する先も asao.gcd.org:http へ変わる。
したがって一つの IP アドレスに
複数のホスト名を持たせるバーチャルドメイン機能を、
任意の SSL 通信で実現できる。
</p>
<p>
なお、上記 stone 実行方法 (オプション指定) は、やや煩雑なので、
</p>
<pre class="code">
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
</pre>
<p>
などと、証明書ファイルの指定をまとめることもできる。
「-z certpat=%n-cert.pem」オプションによって証明書のファイルのパターンを
指定する。
「%n」はサーバのホスト名で置き換えられる。
すなわち、
「-z servername=senri.gcd.org」を指定した場合は、
「-z cert=senri.gcd.org-cert.pem」を指定したのと同じ結果になる。
「-z keypat=%n-key.pem」についても同様。
</p>
<p>
現時点で SNI をサポートしている WWWブラウザは、
私の知っている範囲だと Firefox 2.0 等と IE7 だけであるが、
今後は開発される WWWブラウザの大半が SNI をサポートすることになるだろう。
そうなれば https サーバも、
バーチャルドメインで運用することが一般的となるはずである。
</p>
<blockquote>
Name Based SSLについてもうちょっと腰を入れて調べる。<br />
IE7ではRFC3546 のServer Name Indicationの対応がされている。<br />
Apacheの方では、RFC3546は、mod_gnutlsを入れれば対応は可能なようです。<br />
<a href="http://www.outoforder.cc/projects/apache/mod_gnutls/">mod_gnutlsのサイトはこちら。</a><br />
が、2005年から時が止まったまま・・・。むう。
<div align="right"><a href="http://debz-di.kabocha.to/archives/2007/06/20070616212947.html">でびぞー徒然日記</a>から引用</div>
</blockquote>
<p>
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 だけに限定できる。
</p>
<pre class="terminal">
% 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
</pre>
<p>
以上は stone が SSL サーバとして、
サーバホスト名通知を受付ける場合であるが、
もちろん stone を SSL クライアントとして実行し、
サーバホスト名を通知することもできる。
</p>
<pre class="terminal">
% 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]
</pre>
<p>
「-q servername=asao.gcd.org」オプションで、
通知するサーバホスト名を指定している。
この実行例の場合「asao.gcd.org」を通知しているので、
サーバからは「CN=asao.gcd.org」の証明書が返されている。
この実行例では、中継先が「localhost:443」であるので
「-q servername=」オプションを指定する必要があるが、
もし中継先 (サーバ) ホスト名が通知すべきサーバホスト名に一致するのであれば、
「-q servername=」オプションは省略できる。
</p>
]]></content:encoded>
			<wfw:commentRss>http://www.gcd.org/blog/2007/06/122/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>stone に UDP ⇔ TCP 相互変換機能を実装</title>
		<link>http://www.gcd.org/blog/2007/06/121/</link>
		<comments>http://www.gcd.org/blog/2007/06/121/#comments</comments>
		<pubDate>Fri, 15 Jun 2007 00:05:09 +0000</pubDate>
		<dc:creator>hiroaki_sengoku</dc:creator>
				<category><![CDATA[stone 開発日記]]></category>

		<guid isPermaLink="false">http://www.gcd.org/blog/2007/06/121/</guid>
		<description><![CDATA[stone で UDP と TCP の相互 変換を実装してみた (stone.c 2.3.1.10以降)。 つまり、UDP パケットが届いたら、 その頭にデータ長 (2バイト) を付けて 次図の形式のデータにして TCP [...]]]></description>
			<content:encoded><![CDATA[<p>
stone で UDP と TCP の相互 変換を実装してみた
(<a href="http://cvs.sourceforge.jp/cgi-bin/viewcvs.cgi/stone/stone/stone.c?rev=2.3&amp;view=log">stone.c</a> <a href="http://cvs.sourceforge.jp/cgi-bin/viewcvs.cgi/*checkout*/stone/stone/stone.c?rev=2.3.1.10">2.3.1.10</a>以降)。
つまり、UDP パケットが届いたら、
その頭にデータ長 (2バイト) を付けて
次図の形式のデータにして TCP セッションへ送信する。
あるいは逆に、
TCP セッションから次図の形式のデータを受信したら、
「データ長」の部分を取り除いて
「可変長データ」の部分を UDP パケットとして送信する。
</p>
<pre class="fig">
┌──┬──┬──┬─≪─┬──┐
│データ長　│　可変長データ　　│
└──┴──┴──┴─≫─┴──┘
</pre>
<p>
もちろん「データ長」はネットワーク バイトオーダ (ビッグエンディアン)。
この形式は、
DNS の問合わせ/応答を TCP を使って行なう方法
(<a href="http://www.ietf.org/rfc/rfc1035.txt">RFC1035</a> 4.2.2. TCP usage) と
同じであるので、
</p>
<pre class="code">
# 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
</pre>
<p>
などと stone を実行 (TCP 53 を UDP 53 へ変換) しておいて、
</p>
<pre class="code">
% 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
</pre>
<p>
などと TCP で localhost への問合わせ (-T オプション) を行なうことができる。
すなわち、stone が TCP での問合わせを UDP に変換して
ネームサーバ (192.168.1.1:53/udp) へ中継している。
</p>
<p>
あるいは逆に、
</p>
<pre class="code">
# 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
</pre>
<p>
などと stone を実行 (UDP 53 を TCP 53 へ変換) しておいて、
</p>
<pre class="code">
% 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
</pre>
<p>
などと UDP で localhost へ問合わせることができる。
すなわち、stone が UDP の問合わせを TCP に変換して
ネームサーバ (192.168.1.1:53) へ中継している。
</p>
<p>
この UDP TCP 相互変換機能を、
ssh や <a href="http://sengoku.blog.klab.org/archives/cat_50011671.html">VPN-Warp</a> などを使った
(TCP) ポートフォワードと組合わせることにより、
UDP のポートフォワードが可能になる。
</p>
]]></content:encoded>
			<wfw:commentRss>http://www.gcd.org/blog/2007/06/121/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>La Fonera 上で stone を走らせてみる</title>
		<link>http://www.gcd.org/blog/2007/01/114/</link>
		<comments>http://www.gcd.org/blog/2007/01/114/#comments</comments>
		<pubDate>Thu, 04 Jan 2007 00:32:29 +0000</pubDate>
		<dc:creator>hiroaki_sengoku</dc:creator>
				<category><![CDATA[La Fonera]]></category>
		<category><![CDATA[stone 開発日記]]></category>

		<guid isPermaLink="false">http://www.gcd.org/blog/2007/01/114/</guid>
		<description><![CDATA[La Fonera (FON ソーシャル ルータ) のファーム ウェアのソース コードは、 Fon blog に書かれているように、 http://download.fon.com/firmware/fonera/lat [...]]]></description>
			<content:encoded><![CDATA[<p>
La Fonera (FON ソーシャル ルータ) のファーム ウェアのソース コードは、
<a href="http://blog.fon.com/en/archive/general/la-fonera-source-code.html">Fon blog</a> に書かれているように、
<a href="http://download.fon.com/firmware/fonera/latest/fonera.tar.bz2">http://download.fon.com/firmware/fonera/latest/fonera.tar.bz2</a> から取得できる。
これを展開して make を実行すると、
「OpenWrt Configuration」ダイアログが表示される。
現行の La Fonera のファーム ウェアと同じものをビルドするだけなら
Configuration の変更は不要。そのまま save して exit すればビルドが行なわれる。
</p>
<p>
stone を make するには、
libpthread と libopenssl が必要なので、
以下のように設定する。<br />
まず、
</p>
<pre class="code">
     Base system  --->
</pre>
<p>
を選択して、以下のように libpthread を make するように指定する:
</p>
<pre class="code">
&lt;*&gt; libpthread.......................................... POSIX threa
</pre>
<p>
続いて、
</p>
<pre class="code">
    Libraries  --->
</pre>
<p>
を選択して、以下のように libopenssl も make するように指定しておく:
</p>
<pre class="code">
&lt;M&gt; libopenssl..................................... Open source SSL
&lt; &gt;   openssl-util.............................. OpenSSL command lin
&lt;M&gt; zlib................. Library implementing the deflate compressi
</pre>
<p>
「Do you wish to save your new OpenWrt configuration?」
と聞かれるので「Yes」と答える。
するとクロスコンパイル環境とファームウェアのビルドがどんどん進む。
</p>
<pre class="terminal">
*** 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
</pre>
<p>
「install」と表示されているが、
これは「./staging_dir_mips」ディレクトリへのインストール。
「./staging_dir_mips」にクロスコンパイル環境がインストールされた後に、
これを使ってカーネルやライブラリ等のコンパイルが進む。
</p>
<pre class="terminal">
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
</pre>
<p>
「OpenWrt Configuration」ダイアログで指定しなかった
パッケージまで表示されるが、
これは単に make がそのパッケージのディレクトリの Makefile を実行しただけで、
コンパイルなどは何も行なわずに抜けている。
</p>
<pre class="terminal">
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
</pre>
<p>
以上で、クロスコンパイル環境とファームウェアのビルドが全て完了。
所要時間は 30分ほど (私の Celeron D 345 マシンの場合)。
</p>
<p>
ビルドしたクロスコンパイル環境は、
「staging_dir_mips/bin」に PATH を通すだけで使用できる。
</p>
<p>
stone を make してみる。
まずは CVS レポジトリから最新版を checkout する:
</p>
<pre class="terminal">
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
</pre>
<p>
次に make する:
</p>
<pre class="terminal">
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'
</pre>
<p>
なお、CVS から checkout した最新版でなくても、
例えば stone 2.3c において
</p>
<pre class="terminal">
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
</pre>
<p>
などとコンパイルすることにより La Fonera 版 stone を make することができる。
つまり特に変更することなく make できるわけで、
いかに La Fonera がフツーの Linux マシンであるかが分かる。
</p>
<p>
make した stone および、
libpthread, libopenssl を La Fonera へコピーすれば
(libopenssl は SSL 版 stone の場合のみ必要)、
La Fonera 上で stone を実行することができる。
</p>
<pre class="terminal">
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 &lt;sengoku@gcd.org&gt;
Jan  4 00:18:08.579479 1024 using OpenSSL 0.9.8d 28 Sep 2006  http://www.openssl.org/
Usage: stone &lt;opt&gt;... &lt;stone&gt; [-- &lt;stone&gt;]...
opt:  -h opt            ; help for &lt;opt&gt; more
      -h stone          ; help for &lt;stone&gt;
      -h ssl            ; help for &lt;SSL&gt;, see -q/-z opt
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.gcd.org/blog/2007/01/114/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>stone 2.3c のバグ (select 版の make 方法)</title>
		<link>http://www.gcd.org/blog/2006/11/106/</link>
		<comments>http://www.gcd.org/blog/2006/11/106/#comments</comments>
		<pubDate>Tue, 28 Nov 2006 21:37:54 +0000</pubDate>
		<dc:creator>hiroaki_sengoku</dc:creator>
				<category><![CDATA[stone 開発日記]]></category>

		<guid isPermaLink="false">http://www.gcd.org/blog/2006/11/106/</guid>
		<description><![CDATA[stone 2.3c epoll 版 (つまり -DUSE_EPOLL をつけてコンパイル) において、 proxy 機能にバグを見つけた。 stone を http proxy として使用して、 同じセッション内で異な [...]]]></description>
			<content:encoded><![CDATA[<p>
stone 2.3c epoll 版 (つまり -DUSE_EPOLL をつけてコンパイル) において、
proxy 機能にバグを見つけた。
stone を http proxy として使用して、
同じセッション内で異なるホスト (IP アドレスが異なる) へ
アクセスすると接続できずにタイムアウトする。
ブラウザで「再読み込み」操作を行なえば、
ブラウザが新規セッションを張るので接続できるようになる。
</p>
<p>
<a href="http://cvs.sourceforge.jp/cgi-bin/viewcvs.cgi/stone/stone/stone.c?r1=2.2.4.6&amp;r2=2.2.4.7">stone.c 2.2.4.7</a> にて修正済み。
同一セッションにて異なるホストへ接続しようとしたとき、
stone は旧ソケットをクローズして新しいソケットをオープンするのだが、
このとき EPOLL_CTL_ADD するのを忘れていた。
select 版では新ソケットで FD_SET するので問題はない。
</p>
<p>
なお、epoll 版は Linux カーネルおよび glibc にて
epoll がサポートされていることが必要。
現在使われている Linux ディストリビューションの大半 (?) は、
いまだ EPOLLONESHOT をサポートしていないので、
epoll 版を make することはできず、
次のようなエラーになる:
</p>
<pre class="terminal">
% 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
</pre>
<p>
このようなエラーが出る場合は、
Makefile にて 「-DUSE_EPOLL」 を削除して make することにより、
select 版を作成できる。
</p>
<pre class="terminal">
% 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
</pre>
<p>
このようなパッチを Makefile にあてた後、make すればよい。
</p>
<pre class="terminal">
% 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'
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.gcd.org/blog/2006/11/106/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>stone 2.3c リリース</title>
		<link>http://www.gcd.org/blog/2006/08/97/</link>
		<comments>http://www.gcd.org/blog/2006/08/97/#comments</comments>
		<pubDate>Thu, 17 Aug 2006 23:25:15 +0000</pubDate>
		<dc:creator>hiroaki_sengoku</dc:creator>
				<category><![CDATA[stone 開発日記]]></category>

		<guid isPermaLink="false">http://www.gcd.org/blog/2006/08/97/</guid>
		<description><![CDATA[stone 2.3c を リリースした。 現時点ではスナップショット扱いであるが、重大なバグが発見されない限り、 このバージョンを stone 2.3 に代えて正式リリースとする予定 (今度こそ!)。 stone 2.3 [...]]]></description>
			<content:encoded><![CDATA[<p>
<a href="http://www.gcd.org/sengoku/stone/Welcome.ja.html">stone 2.3c</a> を
リリースした。
現時点ではスナップショット扱いであるが、重大なバグが発見されない限り、
このバージョンを stone 2.3 に代えて正式リリースとする予定 (今度こそ!)。
<a href="http://www.gcd.org/blog/2006/06/80/">stone 2.3b</a> からの
<a href="http://cvs.sourceforge.jp/cgi-bin/viewcvs.cgi/stone/stone/stone.c?r1=2.2.2.26%3ASTONE-2_3B&amp;tr1=1.1&amp;r2=text&amp;tr2=2.2.3.15&amp;diff_format=h">変更点は以下の通り</a>。
</p>
<h3>depth 値に依存せずに root CA 識別名を指定できるように</h3>
<p>
<a href="http://www.gcd.org/blog/2006/08/96/">stone の SSL 認証 (3)</a>
で説明したように、
root CA から数えた階層数で「re数字=正規表現」を指定できるようにした。
root CA を「-1」とし、root CA の子CA が「-2」、以下順に数字が減っていく。
</p>
<h3>busy loop が起きるバグを修正</h3>
<p>
「<a href="http://www.gcd.org/blog/2006/07/89/">stone 2.3b のバグ</a>」
に書いたように、
stone 2.3b には SSL 接続が確立する前に TCP 接続が切れてしまうと、
まれに busy loop が発生するバグがあった。
<a href="http://cvs.sourceforge.jp/cgi-bin/viewcvs.cgi/stone/stone/stone.c?r1=text&amp;tr1=2.2.3.2&amp;r2=text&amp;tr2=2.2.3.12&amp;diff_format=h">stone.c 2.2.3.12</a>
で修正済み。
</p>
<p>
さらに、万一 busy loop が起きたときに、それを検知するコードを追加した。
stone.c 2.2.3.13 には、
busy loop の誤検出するケースがあったので、
<a href="http://cvs.sourceforge.jp/cgi-bin/viewcvs.cgi/stone/stone/stone.c?r1=2.2.3.13&amp;r2=2.2.3.14">stone.c 2.2.3.14</a> で修正した。
</p>
<h3>SSL_accept 前に EOF となった場合にエラーを出さない</h3>
<p>
TCP レベルのヘルスチェックを行なっている場合、
SSL プロトコルを喋らずに shutdown する設定で運用するケースがある。
このような場合に、stone が SSL エラーとして出力してしまうと、
ログが無意味に肥大化してしまう。
そこで accept 直後に EOF になった場合は DEBUG メッセージのみ出力し、
エラーは出力しないようにした
(<a href="http://cvs.sourceforge.jp/cgi-bin/viewcvs.cgi/stone/stone/stone.c?r1=2.2.3.12&amp;r2=2.2.3.13">stone.c 2.2.3.13</a>)。
</p>
<h3>SSL close notify を送信できていなくてもエラーを出さない</h3>
<p>
SSL close notify を送信する前に通信相手が接続を切ってしまうケースがある。
あまり好ましいことではないが、
SSL セッションの再利用ができなくなる点を除けば実害はなく、
行儀が悪い通信相手のためにエラーログを肥大化させるのは割が合わないので、
これもエラーメッセージから DEBUG メッセージへ降格した
(<a href="http://cvs.sourceforge.jp/cgi-bin/viewcvs.cgi/stone/stone/stone.c?r1=2.2.3.12&amp;r2=2.2.3.13">stone.c 2.2.3.13</a>)。
</p>
<h3>「/udp」指定を転送元、転送先とで区別</h3>
<p>
従来は、転送先あるいは転送元のどちらかに「/udp」を指定していれば、
udp の中継を行なっていた。
今後は、udp の中継を行なう場合は、両方に「/udp」を指定する必要がある。
片方のみ「/udp」を指定した場合は、
udp から tcp への、あるいはその逆の変換を行なう指定とする予定(未実装)。
</p>
]]></content:encoded>
			<wfw:commentRss>http://www.gcd.org/blog/2006/08/97/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>stone の SSL 認証 (3)</title>
		<link>http://www.gcd.org/blog/2006/08/96/</link>
		<comments>http://www.gcd.org/blog/2006/08/96/#comments</comments>
		<pubDate>Mon, 14 Aug 2006 23:37:31 +0000</pubDate>
		<dc:creator>hiroaki_sengoku</dc:creator>
				<category><![CDATA[stone 開発日記]]></category>

		<guid isPermaLink="false">http://www.gcd.org/blog/2006/08/96/</guid>
		<description><![CDATA[前々回と 前回で 説明したように、 stone で 通信相手の SSL 認証を行なうには、 通信相手がクライアントの時 (クライアント認証) は、 -z verify -z CApath=ディレクトリ という設定で実行し [...]]]></description>
			<content:encoded><![CDATA[<p>
<a href="http://www.gcd.org/blog/2006/08/94/">前々回</a>と
<a href="http://www.gcd.org/blog/2006/08/95/">前回</a>で
説明したように、
<a href="http://www.gcd.org/sengoku/stone/Welcome.ja.html">stone</a> で
通信相手の SSL 認証を行なうには、
通信相手がクライアントの時 (クライアント認証) は、
</p>
<pre class="code">
-z verify -z CApath=ディレクトリ
</pre>
<p>
という設定で実行し、通信相手がサーバの時 (サーバ認証) は、
</p>
<pre class="code">
-q verify -q CApath=ディレクトリ
</pre>
<p>
という設定で実行する。
「<tt>CApath=...</tt>」で指定したディレクトリに、
root CA 証明書を「ハッシュ値.数字」という形式のファイル名で
置いておくことにより、
通信相手が提示した証明書を検証することができ、
検証に成功したときのみ通信を許可する、
などのアクセス制限を行なうことができる。
</p>
<p>
ある root CA が発行した証明書、
あるいはその root CA の子, 孫, ... CA が発行した証明書、
いずれの証明書でも内容を問わず通信を許可して構わないのであれば、
これ以上の設定は不要であるが、
現実問題としては正規の証明書であれば何でも OK というケースは稀だろう。
</p>
<p>
メジャーな root CA だと、沢山の子 CA を持ち、
それぞれの子がさらに沢山の孫 CA を持っていたりする。
root CA 以外の CA を中間 CA と呼ぶが、
沢山の中間 CA がそれぞれ沢山の証明書を発行しているわけであるから、
その証明書の全てを許可していては、
不特定多数との通信を許可するのと大差ない。
</p>
<p>
そこで SSL 認証では、証明書を検証するだけでなく、
証明書の内容をも確認することが必要となる。
stone では証明書の識別名が満たすべき条件を正規表現の形で指定することができる。
証明書の識別名とは、例えば次のようなものである:
</p>
<pre class="code">
C=JP
ST=Kanagawa
L=Kawasaki
O=GCD
OU=Hiroaki Sengoku
CN=test
emailAddress=sengoku&#64;gcd.org
</pre>
<p>
これを「/」で区切って一行にした文字列:
</p>
<pre class="terminal-scroll">
/C=JP/ST=Kanagawa/L=Kawasaki/O=GCD/OU=Hiroaki Sengoku/CN=test/emailAddress=sengoku&#64;gcd.org
</pre>
<p>
この一行で表現した識別名が満たすべき正規表現を設定する。
この形式は、
<a href="http://www.gcd.org/blog/2006/08/94/">前々回説明した</a>クライアントが
既知の root CA (の子 CA) が発行した証明書を提示した場合の stone の出力例の
中に出てきている:
</p>
<pre class="terminal-scroll">
depth2=/C=JP/ST=Kanagawa/L=Kawasaki/O=GCD/CN=GCD Root CA/emailAddress=root&#64;gcd.org
depth1=/C=JP/ST=Kanagawa/L=Kawasaki/O=GCD/OU=Hiroaki Sengoku/CN=GCD_Sengoku_CA/emailAddress=sengoku&#64;gcd.org
depth0=/C=JP/ST=Kanagawa/L=Kawasaki/O=GCD/OU=Hiroaki Sengoku/CN=test/emailAddress=sengoku&#64;gcd.org
</pre>
<p>
「depth2=」の行が、root CA 「GCD Root CA」の証明書の識別名であり、<br />
「depth1=」の行が、「GCD Root CA」の子CA 「GCD_Sengoku_CA」の証明書の識別名であり、<br />
「depth0=」の行が、「GCD_Sengoku_CA」が発行した証明書の識別名である。<br />
root CA, 子, 孫, 曾孫, ... と、中間CA の階層数が増えて証明書の連鎖が続く場合、
「depth」の数字は大きくなり、
頂点である root CA の「depth」の数字が最も大きくなる。
一方、
サーバないしクライアント自身の証明書は常に「depth0」である。
stone では、depth0 から depth9 まで、扱うことができる。
</p>
<p>
各識別子 「depth0」～「depth9」が満たすべき正規表現を、<br />
それぞれ 「-z re0=正規表現」～「-z re9=正規表現」(クライアント認証の場合) <br />
あるいは 「-q re0=正規表現」～「-q re9=正規表現」(サーバ認証の場合) で指定する。<br />
stone の設定ファイルの一例を次に示す:
</p>
<pre class="code">
-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
</pre>
<p>
指定した全ての「re0=正規表現」～「re9=正規表現」が、
それぞれ証明書連鎖の「depth0」～「depth9」とマッチするときに限り、
stone は接続を許可する。
</p>
<p>
stone 2.3c (近日公開予定) 以降では、
root CA から数えた階層数で「re数字=正規表現」を指定することもできる。
すなわち、root CA を「-1」とし、root CA の子CA が「-2」、
以下順に数字が減っていく。
したがって、上述の設定は以下の設定と等価である:
</p>
<pre class="code">
-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
</pre>
<p>
CA の階層数が 2 (すなわち root CA が depth2) の場合、
「re2=」と「re-1=」、および「re1=」と「re-2=」は、
それぞれ同じ証明書を示す。
両方指定した場合、後者が優先される。
この指定方法を用いると、
中間CA の階層数が一定でなくても、
root CA あるいはその子CA の識別名が満たすべき正規表現を指定することが
可能である。
例えば、
</p>
<pre class="code">
-z re-1="/CN=GCD Root CA/"
-z re0="/CN=test[0-9]*/"
</pre>
<p>
などと指定すると、
中間 CA の階層数にかかわらず、
root CA が「GCD Root CA」であり、
CN が「test[0-9]*」にマッチするような証明書を許可する。
</p>
<p>
(次回に続く)
</p>
]]></content:encoded>
			<wfw:commentRss>http://www.gcd.org/blog/2006/08/96/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>stone の SSL 認証 (2)</title>
		<link>http://www.gcd.org/blog/2006/08/95/</link>
		<comments>http://www.gcd.org/blog/2006/08/95/#comments</comments>
		<pubDate>Sun, 13 Aug 2006 07:19:09 +0000</pubDate>
		<dc:creator>hiroaki_sengoku</dc:creator>
				<category><![CDATA[stone 開発日記]]></category>

		<guid isPermaLink="false">http://www.gcd.org/blog/2006/08/95/</guid>
		<description><![CDATA[前回説明したように、 stone で SSL クライアント認証を行なうには、 -z verify -z CApath=ディレクトリ という設定で実行すればよい。 クライアント認証の代わりにサーバ認証を行ないたい場合、 す [...]]]></description>
			<content:encoded><![CDATA[<p>
<a href="http://www.gcd.org/blog/2006/08/94/">前回説明した</a>ように、
<a href="http://www.gcd.org/sengoku/stone/Welcome.ja.html">stone</a> で
SSL クライアント認証を行なうには、
</p>
<pre class="code">
-z verify -z CApath=ディレクトリ
</pre>
<p>
という設定で実行すればよい。
</p>
<p>
クライアント認証の代わりにサーバ認証を行ないたい場合、
すなわちサーバが提示した証明書を検証して、
通信相手であるサーバが正に意図した通りの相手であるか確認したい場合は、
「<tt>-z</tt>」を「<tt>-q</tt>」に置き換える。
例えば、
ローカルホストの 12345番ポートへの接続を <tt>https://www.klab.org</tt> へ
中継しつつサーバ認証を行なう場合、
次のように実行する:
</p>
<pre class="terminal-scroll">
% 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 &lt;- 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]
</pre>
<p>
この実行例では、
stone を実行した後、ローカルホストの 12345番ポートへ
(telnet 等のプログラムを使って) 接続を行なっている。
すると、stone は www.klab.org の 443番ポートへ接続する。
www.klab.org が提示した証明書を受けて、
stone はその証明書を発行した root CA を調べる。
</p>
<p>
上記実行例 4行目「depth2=...」 に表示されているように、
root CA は「ValiCert Class 2 Policy Validation Authority」である。
この root CA の証明書 (ハッシュ値は bcdd5959) は、
「<tt>/usr/local/ssl/certs</tt>」ディレクトリに
「<tt>bcdd5959.0</tt>」というファイル名で用意してあったので、
stone はサーバ認証を行なうことができている。
</p>
<blockquote>
<a href="http://www.mozilla-japan.org/products/firefox/">Firefox</a> などの
WWWブラウザは、
メジャーな root CA の証明書を最初から全て持っている。
したがって https な URL を開くとき、
自動的にサーバ認証を行なうことができる。
stone の場合はデフォルトの状態では何も root CA 証明書を持っていないので、
サーバ認証/クライアント認証を行なおうとする場合は、
root CA 証明書を置くディレクトリを用意し、
CApath で指定する必要がある。<br />
<br />
なお、WWW ブラウザで普通に「サーバ認証」を行なう場合は、
単にサーバが提示する証明書が既知の root CA から発行されているだけでは
充分ではなく、
その証明書の識別名において、
「CN=...」が示す FQDN がサーバのホスト名と一致しなければならない。<br />
stone でも「CN=...」が示す FQDN をチェックすることができるが、
その方法については
<a href="http://www.gcd.org/blog/2006/08/96/">次回説明する</a>。
</blockquote>
<p>
上記実行例 6行目「depth0=...」が、
サーバが提示したサーバ証明書である。<br />
なお、「CN=*.klab.org」 と表示されていることから分かるように、
このサーバ証明書はワイルドカード証明書であり、
klab.org ドメインの任意のホストの身元証明に使用できる。
</p>
<p>
さて、これで stone を使って通信相手がサーバの時もクライアントの時も、
通信相手を SSL 認証することができるわけであるが、
逆に認証してもらう側はどのような設定を行なえばよいのだろうか？
</p>
<p>
実は、stone がサーバ認証を受ける側であるときの設定方法は、
すでに<a href="http://www.gcd.org/blog/2006/08/94/">前回説明している</a>。
サーバ証明書の秘密鍵および公開鍵が、
それぞれ「<tt>key.pem</tt>」と「<tt>cert.pem</tt>」というファイル名で
保存してあるなら、次のように stone を実行する。
</p>
<pre class="code">
stone -z key=key.pem -z cert=cert.pem localhost:80 443/ssl
</pre>
<p>
stone がクライアント認証を受ける側であるときの設定方法は、
「<tt>-z</tt>」を「<tt>-q</tt>」に置き換えるだけである。
すなわち、
</p>
<pre class="code">
stone -q key=key.pem -q cert=cert.pem warp.klab.org:443/ssl 12345
</pre>
<p>
このように実行しておいて、
ローカルホストの 12345番ポートへ接続すれば、
クライアント認証を要求する https サーバ「<tt>warp.klab.org</tt>」へ
接続することができる(もちろん、key.pem および cert.pem が適切であれば)。
</p>
<p>
(<a href="http://www.gcd.org/blog/2006/08/96/">次回に続く</a>)
</p>
]]></content:encoded>
			<wfw:commentRss>http://www.gcd.org/blog/2006/08/95/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>stone の SSL 認証 (1)</title>
		<link>http://www.gcd.org/blog/2006/08/94/</link>
		<comments>http://www.gcd.org/blog/2006/08/94/#comments</comments>
		<pubDate>Fri, 11 Aug 2006 23:45:52 +0000</pubDate>
		<dc:creator>hiroaki_sengoku</dc:creator>
				<category><![CDATA[stone 開発日記]]></category>

		<guid isPermaLink="false">http://www.gcd.org/blog/2006/08/94/</guid>
		<description><![CDATA[stone の SSL 暗号化/復号の機能は、 おかげさまで沢山の方々に使って頂いている。 任意の TCP/IP 接続を手軽に SSL 化できるので、 SSL 化のためだけに stone を使っている、というかたも多そう [...]]]></description>
			<content:encoded><![CDATA[<p>
<a href="http://www.gcd.org/sengoku/stone/Welcome.ja.html">stone</a>
の SSL 暗号化/復号の機能は、
おかげさまで沢山の方々に使って頂いている。
任意の TCP/IP 接続を手軽に SSL 化できるので、
SSL 化のためだけに stone を使っている、というかたも多そうだ。
</p>
<p>
例えば 25番ポートで接続を受付ける SMTP サーバがある場合、
</p>
<pre class="code">
stone -z key=key.pem -z cert=cert.pem localhost:25 465/ssl
</pre>
<p>
などと実行するだけで、
SSL 化した SMTP を 465番ポートで受付けることができるようになる。
ここで、「<tt>key.pem</tt>」および「<tt>cert.pem</tt>」は、
それぞれ秘密鍵と公開鍵のファイルであり、
OpenSSL のコマンドを使って作ることができる
(日経Linux に以前私が書いた連載記事の
「<a href="http://www.gcd.org/sengoku/docs/NikkeiLinux00-09/SSL.ja.html">第 6 回 stone (後編) 公開かぎと秘密かぎ</a>」でも作り方を説明した)。
</p>
<p>
SSL (Secure Socket Layer) というと
暗号化を思い浮かべる人が多いかも知れないが、
暗号化に負けず劣らず重要なのが、通信相手の認証である。
認証、つまり通信相手の確認をせずに通信内容だけ暗号化しても、
通信相手が意図した相手でなく盗聴者だったりしたら、
少しも「セキュア」ではない。
</p>
<p>
もちろん stone には通信相手を SSL で認証する機能がある。
クライアント認証、すなわち通信相手がクライアントの場合は、
「<tt>-z verify</tt>」を指定し、
サーバ認証、すなわち通信相手がサーバの場合は、<br />
「<tt>-q verify</tt>」を指定する。
</p>
<blockquote>
ちなみに stone の SSL 関連のオプションは
全て「<tt>-z</tt>」か「<tt>-q</tt>」が前につく。
「<tt>-z</tt>」は stone がサーバとして振る舞うときのオプション、
「<tt>-q</tt>」は stone がクライアントとして振る舞うときのオプションである。
通信相手がクライアントの場合というのはつまり stone がサーバとして振る舞う
わけだから、「<tt>-z verify</tt>」になる。
</blockquote>
<p>
前述した「SSL 化した SMTP」を受付ける stone の場合であれば、
通信相手はクライアントであるから、
「<tt>-z verify</tt>」を指定する。
このオプションを指定すると、
stone はクライアントに証明書の提示を要求する。
そして提示された証明書を検証するためには、
その証明書を発行した root CA の証明書が必要である。
</p>
<blockquote>
もちろん、「証明書の提示」といっても、
証明書を送信すれば済む、というものではない。
証明書自体は公開情報 (公開鍵) なので、
それを送信してもクライアントの身の証にはならない。
証明書には対応する秘密鍵があって、
この秘密鍵を持っていることこそがクライアントの身の証である。
SSL プロトコルによってクライアントは秘密鍵をサーバに送信することなく、
自身が秘密鍵を有することをサーバに納得させることができる。<br />
<br />
そして、クライアントの身元を第三者的に証明するのが CA の役割である。
ここで言う CA は、
Certificate Authority (認証局) のことであって、
もちろん Cabin Attendant のことではない。
CA を認証する親 CA といった形で CA の階層構造が作られ、
クライアントの証明書を発行するのは末端の CA であることもあるが、
stone が持っている必要があるのは階層構造の一番上、
root CA の証明書のみである (root つまり「根っこ」)。
root CA の証明書一つで、
root CA の子 CA, 孫 CA, ... 階層構造に含まれる全ての CA が発行した
証明書の検証を行なうことができる。
</blockquote>
<p>
root CA の証明書を置くディレクトリは、
「<tt>-z CApath=ディレクトリ</tt>」で指定する。
例えば「<tt>/usr/local/ssl/certs</tt>」ディレクトリに
root CA の証明書を置く場合は、<br />
「<tt>-z CApath=/usr/local/ssl/certs</tt>」と指定する。
</p>
<p>
stone がクライアントに証明書の提示を要求し、
クライアントがそれに答えた場合、
stone はクライアントが提示した証明書を発行した root CA を調べ、
その識別名の「ハッシュ値」を求める。
root CA の識別名のハッシュ値は、
root CA の証明書のファイル名を「<tt>CA-cert.pem</tt>」とすると、
次のように「openssl x509」コマンドで求めることができる。
</p>
<pre class="terminal">
% openssl x509 -hash -noout -in CA-cert.pem
ee31d843
</pre>
<p>
次に stone は、
「<tt>-z CApath=ディレクトリ</tt>」で指定された証明書ディレクトリ
(この例では「<tt>/usr/local/ssl/certs</tt>」) の中の、
「<tt>ハッシュ値.数字</tt>」というファイル名の証明書を用いて、
クライアントが提示した証明書の検証を行なう。
</p>
<p>
例えば root CA の識別名のハッシュ値が「ee31d843」であり、
「<tt>/usr/local/ssl/certs/ee31d843.0</tt>」が存在するなら、
このファイルを root CA の証明書として用いる。
なぜ ファイル名に「.0」という拡張子をつけるかというと、
この証明書ディレクトリに複数の root CA 証明書を置きたい場合、
「たまたま」ハッシュ値が一致してしまう場合があり得るからである。
その場合は、どちらか一方の証明書の拡張子を「.1」にする。
</p>
<p>
root CA 証明書は、
「<tt>ハッシュ値.数字</tt>」という形式のファイル名でなければ
stone は参照しないが、
かといって「<tt>ハッシュ値.数字</tt>」というファイル名では、
どのファイルがどの root CA の証明書だか分からなくなってしまうので、
次のように root CA 証明書「CA-cert.pem」のシンボリックリンクを
作成しておくとよいだろう。
</p>
<pre class="terminal">
% ln -s CA-cert.pem /usr/local/ssl/certs/ee31d843.0
</pre>
<p>
Windows など、シンボリックリンクが存在しない OS の場合は、
単にコピーしておくだけでもよい。
</p>
<pre class="terminal">
C:\usr\local\ssl\certs> copy CA-cert.pem ee31d843.0
</pre>
<p>
以上、まとめると stone の設定は次のようになる:
</p>
<pre class="code">
-z key=key.pem
-z cert=cert.pem
-z verify
-z CApath=/usr/local/ssl/certs
localhost:25 465/ssl
</pre>
<p>
この設定を例えば stone.config というファイル名で保存し、
「<tt>stone -C stone.config</tt>」などと実行すれば、
正しいクライアント証明書を提示したクライアントからの接続のみ受付け、
証明書を提示しないクライアントや、
証明書ディレクトリに証明書が存在しない root CA が発行したクライアント証明書を
提示したクライアントからの接続を拒否する。
</p>
<p>
クライアントが、既知の root CA (の子 CA) が発行した証明書を提示した場合の例:
<pre class="terminal-scroll">
# 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 &lt;- 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&#64;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&#64;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&#64;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&#64;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&#64;gcd.org]
</pre>
<p>
1行目のコマンドラインで「<tt>-z verbose</tt>」と指定しているが、
これは SSL 関係の詳細ログを表示させるためのオプションである。
「<tt>depth2=...</tt>」(4行目) が、
root CA の証明書の識別名である。
この証明書は stone にとって既知の証明書なので、
検証が成功する。
「<tt>depth1=...</tt>」(5行目) は、
root CA の子 CA の証明書の識別名であり、
「<tt>depth0=...</tt>」(6行目) は、
クライアントの証明書である。
</p>
<p>
クライアントが、未知の CA が発行したクライアント証明書を提示した場合の例:
<pre class="terminal-scroll">
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&#64;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
</pre>
<p>
1行目に、別の root CA の識別名を表示しているが、
この root CA は証明書ディレクトリの中に証明書が存在しないので、
この stone にとって未知である。
したがって、2行目
「verify error err=19 self signed certificate in certificate chain」
つまり「クライアントが自分で署名した証明書 (いわゆる「オレオレ証明書」) を
提示した」と、
検証に失敗した旨のエラーメッセージが出力されている。
クライアントからの接続はこの直後に切断される。
</p>
<p>
(<a href="http://www.gcd.org/blog/2006/08/95/">次回に続く</a>)
</p>
]]></content:encoded>
			<wfw:commentRss>http://www.gcd.org/blog/2006/08/94/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>stone 2.3b のバグ</title>
		<link>http://www.gcd.org/blog/2006/07/89/</link>
		<comments>http://www.gcd.org/blog/2006/07/89/#comments</comments>
		<pubDate>Fri, 14 Jul 2006 03:04:25 +0000</pubDate>
		<dc:creator>hiroaki_sengoku</dc:creator>
				<category><![CDATA[stone 開発日記]]></category>

		<guid isPermaLink="false">http://www.gcd.org/blog/2006/07/89/</guid>
		<description><![CDATA[重大なバグが発見されない限り正式リリースとする予定だった stone 2.3b であるが、 あいにくバグが発見されてしまった。 SSL 接続が確立する前に TCP 接続が切れてしまうと、 まれに busy loop が発 [...]]]></description>
			<content:encoded><![CDATA[<p>
重大なバグが発見されない限り正式リリースとする予定だった
<a href="http://www.gcd.org/blog/2006/06/80/">stone 2.3b</a> であるが、
あいにくバグが発見されてしまった。
SSL 接続が確立する前に TCP 接続が切れてしまうと、
まれに busy loop が発生する。
某環境では、health check と称して ;-(
ごく短い時間でポートのオープン/クローズを繰り返しているので、
何日か走らせると load average が無駄に 1 増えてしまっていた。
逆に言うと、かなり無茶な使い方をしない限り、この現象は起きないと思われる。
</p>
<p>
なぜ busy loop が起きるかと言えば、
doReadWrite() select loop において、
close 予定のソケットについて書込み待ち FD_SET して
select(2) を行なうケースがあったからだ。
close 予定のソケットだから、書込み可能でも何も書込まない。
だから select(2) は待ち時間 0 で常に書込み可能を返す。
これが延々繰り返される。
</p>
<p>
この現象を回避するには、
close 予定のソケットについては FD_SET しなければよい。
ただし SSL_shutdown(3) が書込み待ちでブロックすることがあるので、
この場合、すなわち sf_sb_on_w (SSL flag: shutdown blocked on write) が
セットされているときは、
close 予定であっても FD_SET する。
<a href="http://cvs.sourceforge.jp/cgi-bin/viewcvs.cgi/stone/stone/stone.c?r1=text&amp;tr1=2.2.3.2&amp;r2=text&amp;tr2=2.2.3.6&amp;diff_format=h">stone.c 2.2.3.6 で修正済み</a>。
なお、busy loop は select 版でのみ確認したが、
epoll 版でも発生する可能性があると思われる。
今回の修正で epoll 版でも busy loop を防ぐことができると思われる。
</p>
<p>
ついでに、busy loop が起きたときにそれを検知するコードも追加した。
doReadWrite() select loop において busy loop が発生すると、
「<tt>&lt;sd&gt; TCP &lt;sd&gt;: doReadWrite Can't happen spin occured</tt>」
というエラーログを出力する。
stone が出力するログで「Can't happen」から始まるものは、
想定外の事象が起きたことを示す。
つまりバグである。
stone の最新版において、このようなログに遭遇したかたは、
ご連絡頂けると大変ありがたい。
</p>
]]></content:encoded>
			<wfw:commentRss>http://www.gcd.org/blog/2006/07/89/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>stone 2.3b リリース</title>
		<link>http://www.gcd.org/blog/2006/06/80/</link>
		<comments>http://www.gcd.org/blog/2006/06/80/#comments</comments>
		<pubDate>Sat, 17 Jun 2006 13:02:41 +0000</pubDate>
		<dc:creator>hiroaki_sengoku</dc:creator>
				<category><![CDATA[stone 開発日記]]></category>

		<guid isPermaLink="false">http://www.gcd.org/blog/2006/06/80/</guid>
		<description><![CDATA[stone 2.3b をリリースした。 現時点ではスナップショット扱いであるが、 重大なバグが発見されない限り、 このバージョンを stone 2.3 に代えて正式リリースとする予定。 stone 2.3a からの変更点 [...]]]></description>
			<content:encoded><![CDATA[<p>
<a href="http://www.gcd.org/sengoku/stone/Welcome.ja.html">stone 2.3b</a> をリリースした。
現時点ではスナップショット扱いであるが、
重大なバグが発見されない限り、
このバージョンを stone 2.3 に代えて正式リリースとする予定。
stone 2.3a からの変更点は以下の通り。
</p>
<h3>doReadWrite select ループを抜けるバグを修正</h3>
<p>
<a href="http://cvs.sourceforge.jp/cgi-bin/viewcvs.cgi/stone/stone/stone.c?r1=2.2.2.5&amp;r2=2.2.2.6">stone.c  2.2.2.6</a> で
doReadWrite から doReadWritePair を分離したときに作りこんでしまったバグを修正。
</p>
<p>
このバグにより、意図せず doReadWrite select ループを抜けてしまい、
パフォーマンスが大幅に低下するケースがあった。
詳しくは <a href="http://www.gcd.org/blog/2006/06/73/">SSL_Pending</a> を参照。
</p>
<h3>アーカイブから stone.1 と stone.1.ja を削除</h3>
<p>
stone 2.3 と stone 2.3b との仕様の差分を、
マニュアル stone.1 および stone.1.ja に反映できていないので、
とりあえず削除。
</p>
<p>
現時点での stone の正式マニュアルは、
パッケージ同梱の README.txt および README.en.txt である。
</p>
<h3>変数名の変更など</h3>
<p>
関数の仮引数と同じ変数名のローカル変数を使っていた。
<a href="http://cvs.sourceforge.jp/cgi-bin/viewcvs.cgi/stone/stone/stone.c?r1=2.2.2.22&amp;r2=2.2.2.23">stone.c  2.2.2.23</a> で修正。
</p>
]]></content:encoded>
			<wfw:commentRss>http://www.gcd.org/blog/2006/06/80/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>SSL_pending</title>
		<link>http://www.gcd.org/blog/2006/06/73/</link>
		<comments>http://www.gcd.org/blog/2006/06/73/#comments</comments>
		<pubDate>Fri, 02 Jun 2006 09:51:48 +0000</pubDate>
		<dc:creator>hiroaki_sengoku</dc:creator>
				<category><![CDATA[stone 開発日記]]></category>

		<guid isPermaLink="false">http://www.gcd.org/blog/2006/06/73/</guid>
		<description><![CDATA[SSL_pending をマニュアルで調べると、 SSL_pending - obtain number of readable bytes buffered in an SSL object SSL_pending() [...]]]></description>
			<content:encoded><![CDATA[<p>
SSL_pending をマニュアルで調べると、
</p>
<blockquote>
SSL_pending - obtain number of readable bytes buffered in an SSL object<br />
<br />
SSL_pending() returns the number of bytes which are available
inside ssl for immediate read.
</blockquote>
<p>
と書いてある。つまり OpenSSL ライブラリ内に受信可能データがある場合、
そのデータバイト数を返す関数である。
</p>
<p>
なぜこんな関数が必要かというと、
データを送信しようとして SSL_write を呼び出したときも、
TCP/IP レベルでは送信だけでなく、受信も行なわれるからだ。
SSL のような暗号通信の場合、
送信は「垂れ流し」では済まず、ハンドシェークを行なう必要があるからだが、
この時、受信しようと思っていなかったデータ、
つまり通信相手が送信したデータまで読み込んでしまう場合がある。
</p>
<p>
すると、SSL_write を呼んでいるのに、
OpenSSL の受信バッファに、意図せずデータが溜まってしまう。
こうなってしまうと、select(2) や epoll(2) では検知できない。
select(2) や epoll(2) は、
I/O レベルでの受信データの有無を調べるシステムコールであり、
それより上のレベルである OpenSSL ライブラリの受信バッファのことは
関知しないからだ。
</p>
<p>
stone では、SSL_write で全てのデータを送り終わったとき、
SSL_pending を呼び出して受信すべきデータがないか確認している。
これを行なっているのが、
中継元/中継先との送受信を行なう stone の中核関数
doReadWritePair である。
</p>
<p>
select 版 stone の場合、
doReadWritePair を呼び出すのは doReadWrite である。
この関数は、
中継元/中継先との通信を行なうソケットディスクリプタのみを監視する
select ループである。
epoll 版と異なり、select 版では、
select で監視するソケットの数が増えるとパフォーマンスが落ちるので、
送受信が継続しているときはメインの select ループとは別のスレッドを作成して、
パフォーマンスの低下を回避している。
送受信が途絶える (0.1 秒以上送受信が行なわれない) と、
この doReadWrite select ループを抜け、スレッドを終了して、
メイン select ループでソケットを監視する状態に戻る。
</p>
<p>
stone 2.3a (正確に言うと、stone.c 2.2.2.6 ～ 2.2.2.23) では、
SSL_pending で受信すべきデータを検知したとき、
SSL_read を行なってデータを受信した直後に、
この doReadWrite select ループを抜けてしまっていた。
本来なら、データを受信したのだから
直ちにそれを中継するために送信しなければならないのであるが、
ループを抜けてしまったために、いったんスレッドを終了し、
メイン select ループで送信可能か確認し、
再びスレッドを生成して doReadWrite select ループに入る、
という無駄が生じてしまっていた。
</p>
<p>
したがって、SSL_pending でデータが検知されることが多発するような場合は、
転送速度が著しく遅くなる。
例えば、stone の受信バッファを意図的に小さくすることによって
OpenSSL 内のバッファに受信データが残りやすくして
SSL 通信を受信してみる:
</p>
<pre class="code">
-X 512
-z key=key.pem
-z cert=cert.pem
localhost:22 localhost:12346/ssl --
</pre>
<p>
「-X 512」オプションによって、受信バッファのサイズを 512 バイト
(デフォルトは 2048 バイト) にしている。
12346番ポートで SSL 接続を受付け、
それを復号した上で、22番ポートへ中継する設定である。
続いて、
</p>
<pre class="code">
localhost:12346/ssl localhost:12347 --
</pre>
<p>
という設定で stone を走らせて、12347番ポートで受付けた接続を、
SSL で暗号化した上で 12346番ポートへ転送するようにする。
上記二つの stone を実行することによって、
12347番ポートへ接続すると、
後者の stone によっていったん SSL で暗号化された上で、
前者の stone で復号されて 22番ポートへつながる。
つまり 12347番ポートで ssh 通信が行なえる。
</p>
<p>
前者の stone として stone 2.3a select 版を使うと、
ssh 転送が異様に遅くなる。
</p>
<pre class="code">
% scp -P 12347 /boot/linuz-2.6.16.18 localhost:/tmp/
...
linuz-2.6.16.18 &ensp; 100% 1211KB  11.3KB/s  01:47
</pre>
<p>
延々 2 分近くかかってしまうが、
最新の stone.c 2.2.2.25 を使って select 版を作ると、
1 秒以内に転送が終わる。
また、stone 2.3a select 版であっても「-X 512」オプションを指定しなければ、
SSL_pending でデータが検知されるケースがほとんどなくなるので、
1 秒以内に転送が終わる。
なお、stone 2.3a epoll 版には、doReadWrite ループがなく、
全てメインループで処理を行なうので、
このような問題はない。
</p>
]]></content:encoded>
			<wfw:commentRss>http://www.gcd.org/blog/2006/06/73/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>stone 2.3a リリース</title>
		<link>http://www.gcd.org/blog/2006/05/61/</link>
		<comments>http://www.gcd.org/blog/2006/05/61/#comments</comments>
		<pubDate>Sat, 06 May 2006 11:54:30 +0000</pubDate>
		<dc:creator>hiroaki_sengoku</dc:creator>
				<category><![CDATA[stone 開発日記]]></category>

		<guid isPermaLink="false">http://www.gcd.org/blog/2006/05/61/</guid>
		<description><![CDATA[stone 2.3a をリリースした。 stone 2.3 からの変更点は以下の通り。 二重 free するバグの修正 stone 2.3 における最も重大なバグ。 同時に大量に socket を close する、とい [...]]]></description>
			<content:encoded><![CDATA[<p>
<a href="http://www.gcd.org/sengoku/stone/Welcome.ja.html">stone 2.3a</a> をリリースした。
stone 2.3 からの変更点は以下の通り。
</p>
<h3>二重 free するバグの修正</h3>
<p>
stone 2.3 における最も重大なバグ。
同時に大量に socket を close する、という利用パターンにて、
二つのスレッドが同時に同じ ExBuf に対して ungetExBuf を行なってしまうために、
二重 free が起きてしまう。
<a href="http://cvs.sourceforge.jp/cgi-bin/viewcvs.cgi/stone/stone/stone.c?r1=2.2.1.10&amp;r2=2.2.1.11">stone.c 2.2.1.11</a> で修正
(わずか一行の修正だが極めて重要)。
</p>
<p>
Pair を thread で処理しているときは scanClose 対象から外す。
</p>
<p>
thread 処理中は、そもそも close 要求が出ていないわけで、
当然 scanClose 対象から外れるはず、と思っていたが、
thread 処理中に proto_close をセットすることもあるわけで、
dowrite が ungetExBuf を呼ぶタイミングと、
scanClose が freePair を呼び出して ungetExBuf を呼ぶタイミングとが
重なる可能性があると思われるので、その防止策。
</p>
<h3>socket を close しないバグの修正</h3>
<p>
SSL_accept に失敗したとき、すなわち TCP レベルでの accept は成功したが、
続く SSL ハンドシェークで失敗した時に、
中継先の socket が open しっぱなしになるバグ。
これは、中継元 socket の accept に成功した時点で、
中継先の socket を open するが、
SSL_accept に失敗したときに close していなかったために起きた問題。
<a href="http://cvs.sourceforge.jp/cgi-bin/viewcvs.cgi/stone/stone/stone.c?r1=2.2.1.7&amp;r2=2.2.1.8">stone.c 2.2.1.8</a> で修正
(これもわずか一行の修正だが重要)。
</p>
<p>
SSL ハンドシェークを行なわない TCP レベルのヘルスチェックを
stone の中継元ポートに対して行なったとき、
stone が open した socket がどんどん増え、
too many open files エラーが起きて accept 出来なくなったことにより発覚。
</p>
<p>
SSL_accept に失敗したとき、pair2 側に close 要求出すのを忘れていた。
SSL_accept に時間がかからない時 (localhost からの接続時) は、doaccept 内で
freePair するので問題にならないが、
SSL_accept に時間がかかる時は、doReadWrite 内で
p[i]->proto |= proto_close; するので、この時
p[1-i]->proto |= proto_close; も行なう必要がある。
</p>
<h3>epoll 対応 (select 版も大幅に性能向上)</h3>
<p>
select(2) の代わりに epoll(2) も利用できるようにした。
コンパイルオプション「-DUSE_EPOLL」をつけることにより epoll 版を make できる。
linux 2.6 以上のカーネルと、
glibc 2.3 以上が必要。
</p>
<p>
epoll 対応のため、stone のコード全体の見直しを行なった。
結果、(epoll を使わない) select 版も大幅にパフォーマンスが向上した。
「<a href="http://www.gcd.org/blog/2006/04/27/">stone 2.3 が遅い理由</a>」および
「<a href="http://www.gcd.org/blog/2006/04/28/">stone 2.3a (候補) ベンチマーク</a>」を参照。
</p>
<h3>アドレスキャッシュ機能の実装</h3>
<p>
Windows 2000 などで、gethostbyname が遅いという問題があるようなので、
proxy で resolv した IP アドレスをキャッシュする。
<a href="http://cvs.sourceforge.jp/cgi-bin/viewcvs.cgi/stone/stone/stone.c?r1=2.2.2.10&amp;r2=2.2.2.11">stone.c 2.2.2.11</a> で実装。
</p>
<h3>unix domain socket で uid 等の取得</h3>
<p>
<a href="http://www.gcd.org/blog/2006/04/40/">SO_PEERCRED</a> 参照。
<blockquote>
&ensp; &lt;host&gt;:&lt;port&gt;/http &lt;sport&gt; &lt;request&gt; [&lt;xhost&gt;...]<br />
<br />
http リクエストにのせて中継します。&lt;request&gt; は、
HTTP 1.0 で規定されるリクエストです。
リクエスト文字列中、
「\」はエスケープ文字であり、
次のような置き換えが行なわれます。<br />
<br />
&ensp; \u &ensp; 接続元のユーザID (番号)<br />
&ensp; \U &ensp; 接続元のユーザ名<br />
&ensp; \g &ensp; 接続元のグループID (番号)<br />
&ensp; \G &ensp; 接続元のグループ名<br />
</blockquote>
<p>
<a href="http://cvs.sourceforge.jp/cgi-bin/viewcvs.cgi/stone/stone/stone.c?r1=2.2.2.17&amp;r2=2.2.2.18">stone.c 2.2.2.18</a> で実装。
</p>
<h3>ミリ秒単位のログ出力</h3>
<p>
<a href="http://cvs.sourceforge.jp/cgi-bin/viewcvs.cgi/stone/stone/stone.c?r1=2.2.2.11&amp;r2=2.2.2.12">stone.c 2.2.2.12</a> および
<a href="http://cvs.sourceforge.jp/cgi-bin/viewcvs.cgi/stone/stone/stone.c?r1=2.2.2.16&amp;r2=2.2.2.17">stone.c 2.2.2.17</a> で実装。
</p>
<h3>health HELO コマンドの分割</h3>
<p>
<a href="http://cvs.sourceforge.jp/cgi-bin/viewcvs.cgi/stone/stone/stone.c?r1=2.2.2.12&amp;r2=2.2.2.13">stone.c 2.2.2.13</a> で実装。
</p>
]]></content:encoded>
			<wfw:commentRss>http://www.gcd.org/blog/2006/05/61/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>EPOLL_CTL_ADD を行うタイミング</title>
		<link>http://www.gcd.org/blog/2006/05/56/</link>
		<comments>http://www.gcd.org/blog/2006/05/56/#comments</comments>
		<pubDate>Mon, 01 May 2006 09:24:47 +0000</pubDate>
		<dc:creator>hiroaki_sengoku</dc:creator>
				<category><![CDATA[stone 開発日記]]></category>

		<guid isPermaLink="false">http://www.gcd.org/blog/2006/05/56/</guid>
		<description><![CDATA[epoll版 stone で EPOLL_CTL_ADD を行うタイミングについて。 基本的には doAcceptConnect 内で行う。 つまり、中継元からの接続を accept し、 中継先へ connect する [...]]]></description>
			<content:encoded><![CDATA[<p>
epoll版 stone で EPOLL_CTL_ADD を行うタイミングについて。
</p>
<p>
基本的には doAcceptConnect 内で行う。
つまり、中継元からの接続を accept し、
中継先へ connect するルーチン。
このルーチンは、Pair リストへの挿入 (insertPairs) を行う
唯一のルーチンでもある。
このルーチンで EPOLL_CTL_ADD を行わないのは、以下の場合のみ。
</p>
<dl>
<dt>(1) proto_close フラグが立っている場合</dt>
<dd>エラー等の原因で close 要求が行われている場合、
中継の必要がないから、EPOLL_CTL_ADD も行わない。</dd>
<dt>(2) 中継先の Pair かつ proto_noconnect フラグが立っている場合</dt>
<dd>proto_noconnect フラグが立っているのは
中継を行わない Pair であることを示す。<br />
この場合は、EPOLL_CTL_ADD を行う必要はない。
</dl>
<p>
ところが、stone を proxy として用いるとき、
proto_noconnect フラグが立つ。
これは中継元からリクエストヘッダを受け取るまでは
中継先が決まらないため。
このとき、(2) に該当するので EPOLL_CTL_ADD が行われない。
</p>
<p>
このため、stone.c 2.2.2.20 では、proxy として用いたとき EPOLL_CTL_ADD が行われず、
中継先への connect 完了を検知できなくなってしまっていた。
</p>
<p>
中継先が決まった段階で EPOLL_CTL_ADD を行うよう修正。
</p>
<p>
$Id: stone.c,v 2.2.2.21 2006/04/29 07:32:28 hiroaki_sengoku Exp $
</p>
]]></content:encoded>
			<wfw:commentRss>http://www.gcd.org/blog/2006/05/56/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

