2010/02/28

racoonによるIPsec環境の構築

freeradiusを触った時に経路の暗号化をしないと潜在的なリスクがあると思ったので、IPsecをtransport modeで試してみる事にしました。

今回の鍵作成の作業はApache2用にSSLキーを作成した時と同じで、Ubuntu 8.04 LTSで行なっています。作成する鍵ファイルは次の3種類です。

  • newreq.pem - リクエストファイル。秘密鍵ファイルを作成するために使用
  • newkey.pem - 秘密鍵ファイル。newreq.pemを元にCAが署名し、生成される
  • newcert.pem - 公開鍵ファイル。newkey.pemと対になる

opensslやCA.plを使った鍵作成の手順を説明したサイトはいくつかありますが、独自のファイル名を使うため鍵ファイルの用途がわかりずらいと感じました。 この記事ではCA.plが作成するファイル名を踏襲しました。

今回の作業全体の流れは7.3.3.2 公開鍵を使った方法にある流れに従っています。 ただ、これだけでは不十分だったので、より正確な情報をIPsec HowtoThe PKIX certificate with racoon、それにipsec-tools.sourceforge.netからのリンク先にあるAutomatic keyingから補完しました。

なおIPsecが稼働するサーバはAlixとWRAPで稼働するDebian lennyです。

鍵ファイルの作成

CAとしてはApacheサーバ用にSSL用鍵ファイルを作成した時に使ったdemoCAをずっと使っていますが、手を広げ過ぎたので鍵ファイルの管理が煩雑になってきました。

そこで今回はIPsec用に中間CAを作成するところから始めました。 必要がなければ飛して既にあるdemoCAディレクトリを使えば良いと思います。

中間CA用demoCAの作成

まずは/usr/lib/ssl/misc/CA.plを使って、土台となるdemoCAディレクトリを準備します。 いま親CAのdemoCAディレクトリがみえるディレクトリにいるとして、中間CA用にサブディレクトリを作成する事にします。

$ mkdir demoCA.ipseq_ca
$ cd demoCA.ipseq_ca
$ /usr/lib/ssl/misc/CA.pl -newca

入力項目は任意なのでパスワードだけ忘れないようにしておきます。 この作業が終るとカレントディレクトリにdemoCAディレクトリが作成されています。

親CAによる中間CA用reqファイルの署名

いまはdemoCA.ipseq_caディレクトリ直下にいて、そこには"demoCA"ディレクトリだけが存在している状態を仮定します。

このディレクトリの中で単独のCAとして必要なものは揃っていますが、中間CAとなるために親CAに署名をしてもらう必要があります。 通常CAを作成するときに使うCA.pl -newcaコマンドでは、リクエストファイルを作成して、リクエストファイルへの署名をdemoCA内にある自身の秘密鍵で行なっています。

今回はその署名作業を親CAに行なってもらう必要があるので、いちど作成したcacert.pemを捨てて、改めてcareq.pemファイルを親CAに署名してもらいます。

$ cp demoCA/careq.pem ../newreq.pem
$ cd ..
$ /usr/lib/ssl/misc/CA.pl -signCA

ここまでの作業で"newreq.pem"ファイルが新たに作成されました。 これを中間CAのdemoCAディレクトリにコピーします。

$ cd demoCA.ipseq_ca
$ rm demoCA/cacert.pem 
$ mv ../newcert.pem demoCA/cacert.pem

ここでcacert.pemを削除せずに上書きしても良いのですが、作業内容をはっきりとさせるために削除するようにしています。

作業が終ったらnewreq.pemファイルは削除しておきます。

$ rm ../newreq.pem

これで中間CAを作成する作業は完了です。

IPsec用の鍵ファイルの作成

中間CAはできたので、IPsecで2点間の通信を暗号化するための鍵ファイルを作成します。 ここでは最終的に"newkey.pem", "newcert.pem"の2つのファイルを1セットとして作成します。 サーバ2台でテストをするために、合計で4ファイル、2セットを作成する事になります。

$ /usr/lib/ssl/misc/CA.pl -newreq
$ /usr/lib/ssl/misc/CA.pl -sign
$ mkdir server1
$ mv new* server1/

作業ではCommon Name(CN)をサーバのFQDNにしておきます。 作業の結果作成された"newkey.pem", "newcert.pem"ファイルをサーバ毎のディレクトリに分けておき、必要に応じて使う事にします。

続けて"server1"を"server2"と読み替えて、もう一度、この手順を繰り返します。

最後にserver1ディレクトリにserver2のnewcert.pemファイルをコピー(server2.newcert.pem)し、server2ディレクトリにも同様にserver1のnewcert.pemファイルをコピー(server1.newcert.pem)します。

ここまででserver1, server2ディレクトリにそれぞれ"newkey.pem"と"newcert.pem"ファイルと相手のnewcert.pemファイルが存在しているはずです。 treeコマンドを使うと、次のようなディレクトリ構造になっているはずです。

treeコマンド出力

|-- demoCA ## 親CA
|   |-- cacert.pem
.   .
.   .
.   `-- careq.pem
.   
`-- demoCA.ipsec_ca ## 中間CA
    |-- demoCA
    .
    .
    |-- server1/
    |   |-- newcert.pem
    |   |-- server2.newcert.pem
    |   `-- newkey.pem
    `-- server2/
       |-- newcert.pem
       |-- server1.newcert.pem
       `-- newkey.pem

実際のところはnewcert.pemとnewkey.pemが2組作れていれば良いので、この構造は参考に留めておいてください。 いろいろな鍵ファイルを作る手順をみていると、どこまでがMUSTで、どこからがSHOULDなのか判らないですよね…。

公開鍵方式を使ったIPsec接続

今回はracoonを使った鍵交換を行なうため、双方のシステムにracoonをインストールしておきます。

$ sudo apt-get install racoon
鍵ファイルの配置

サーバにserver1, server2ディレクトリをそれぞれコピーし、作成した鍵ファイルを/etc/racoon/certsディレクトリにコピーしておきます。 なお本当の環境では、サーバの秘密鍵"newkey.pem"はネットワークを経由する事はありません。相手側に渡さないように注意しましょう。

$ sudo mkdir /etc/racoon/certs
$ sudo cd server1
$ sudo cp newkey.pem newcert.pem server2.newcert.pem /etc/racoon/certs/

他方のserver2サーバにログインして、同様にファイルを配置します。

racoon.confファイルの作成

ファイルの設定方法は本家サイトのドキュメントが信頼性が高そうです。 この他のドキュメントはKAMEの流れを組む*BSD用だったり、少し古いとそのまま使えないものもありました。

exchange_mode main;を使う場合には、IPアドレスでの指定だけが使えるため、IPv6とIPv4の設定を分けていますが、内容は同じです。

server2側の/etc/racoon/racoon.confファイル

path certificate "/etc/racoon/certs";
remote 192.168.1.xx {
        exchange_mode main;
        my_identifier asn1dn;
        peers_identifier asn1dn;

        certificate_type x509 "newcert.pem" "newkey.pem";
	peers_certfile x509 "server1.newcert.pem";
	verify_cert on;
        proposal {
                encryption_algorithm aes;
                hash_algorithm sha1;
                authentication_method rsasig;
                dh_group modp1024;
        }
        generate_policy on;
	passive off;
}
remote 2001:03xx:xxxx:1::xxxx {
        exchange_mode main;
        my_identifier asn1dn;
        peers_identifier asn1dn;

        certificate_type x509 "newcert.pem" "newkey.pem";
        peers_certfile x509 "server1.newcert.pem";
	verify_cert on;
        proposal {
                encryption_algorithm aes;
                hash_algorithm sha1;
                authentication_method rsasig;
                dh_group modp1024;
        }
        generate_policy on;
        passive off;
}

sainfo anonymous {
        pfs_group modp768;
        encryption_algorithm rijndael, 3des;
        authentication_algorithm hmac_sha1, hmac_md5;
        compression_algorithm deflate;
}
CRLファイルの取り扱いについて

いまのところは、CRLやcacert.pemファイルの取り扱いについて、ちゃんとしたドキュメントをみつけられていません。 racoon.confのオプションには関連しそうなca_typeパラメータがありますが、使い方について明確な指示はみつけられませんでした。verify_certは相手方から送られてくるDNと手元のcertファイルのDNを比較するもののようで関係なさそうでした。

コードを少し追った限りではCRLのチェックはしているようにみえました。 しかしrevokeしたnewcert.pemファイルはpeers_certfileに指定した状態で、CRLファイルを配置してもhashファイルへのリンクを作成しても通信が拒否される事ありませんでした。

ハッシュファイルへのシンボリックリンクはc_rehashコマンドで行なうことができます。

$ sudo c_rehash /etc/racoon/certs

ここら辺の動きについてはもう少し確認が必要そうです。

/etc/racconディレクトリの内容

treeコマンドを使うと、現在の/etc/racoonディレクトリの構造は次のようになっています。

tree /etc/racoonの実行結果

/etc/racoon/
|-- certs
|   |-- server2.newcert.pem
|   |-- newcert.pem
|   `-- newkey.pem
`-- racoon.conf
ipsec-tools.confファイルの編集

少し古いドキュメントには/etc/setkey.confを編集する手順が載っていますが、Debian Lennyでは/etc/ipsec-tools.confファイルを編集します。 起動時に/etc/init.d/setkeyスクリプトが、/etc/rc2.d/S??setkey経由で実行されて、ipsec-tools.confの内容が反映されることになります。

/etc/ipsec-tools.confファイル

#!/usr/sbin/setkey -f
#
flush;
spdflush;

## out rule (local -> remote)
## IPv4
spdadd 192.168.10.xx 192.168.1.yy any -P out ipsec
           esp/transport//require;
## IPv6
spdadd 2001:03xx:xxxx:10::xxxx 2001:03xx:xxxx:1::yyyy any -P out ipsec
           esp/transport//require;

## in rule (remote -> local)
## IPv4
spdadd 192.168.1.yy 192.168.10.xx any -P in ipsec
           esp/transport//require;
## IPv6
spdadd 2001:03xx:xxxx:1::yyyy 2001:03xx:xxxx:10::xxxx any -P in ipsec
           esp/transport//require;
稼働確認

両方のサーバで次のようにプロセスを再起動する事で設定が完了します。

$ /etc/init.d/setkey restart
$ /etc/init.d/racoon restart

さいごに

IPsecを使ってみて気になったことをまとめておきます。

煩雑な設定

相手方を指定するところでは個別にpeers_certfileなどを指定する必要があるため、サイト全体に拡張しようとするとかなり煩雑になりそうです。 ファイルを互いに交換する仕組みだと鍵交換がn*(n-1)回必要になるので公開鍵方式の利点を殺したような状態になっています。

まぁネットワーク層レベルの仕組みですから、何を信頼するかという議論もあると思うんですけどね。

とはいえracoon.confファイルはincludeで、外部ファイルを読み込む事ができるので、他で検証した設定ファイルとpemファイル一式を展開してあげれば、だいたい安全に自動化ができそうな気配です。

しかしpeers_certfileはdnssecを引数に取るので、これが使えるかもしれないと思っています。 時間をみつけて検証していこうと思います。

おかしな挙動

試したdebian lenny同士の接続では、ipsec-tools.confでAHを有効(ah/transport//require;付き)にした状態で、しばらく使っていると反応がなくなりハングアップしてしまいました。

うまく動かなかったipsec-tools.conf設定

...
## IPv6
spdadd 2001:03xx:xxxx:10::xxxx 2001:03xx:xxxx:1::yyyy any -P out ipsec
    esp/transport//require
    ah/transport//require;
...

これはpmtu関連のメッセージが大量にでていて、どうやら途中の経路にあるルータのMTUが1500よりも低いことが原因のようでした。 このルータをはさまないようにスイッチングハブのみを経由して互いを接続したところ、この問題は発生しませんでした。

ただIPv4は無事に接続できたのですが、IPv6だけはエラーになってしまいました。

racoonのエラーメッセージ

...
Mar  1 08:59:26 localhost racoon: ERROR: phase1 negotiation failed due to time up. ac92e25b5eb92084:0000000000000000
Mar  1 08:59:37 localhost racoon: ERROR: phase2 negotiation failed due to time up waiting for phase1. ESP 2001:3e0:a31:10:20d:b9ff:fe12:f284[500]->2001:3e0:a31:10:20d:b9ff:fe03:4154[500]
...

これはracoonが正常に動いていないようにみえるのですが、ともかく設定を再び行ないます。

$ sudo /etc/init.d/racoon stop
$ sudo /usr/sbin/setkey -F
$ sudo /usr/sbin/setkey -FP

2,3分ほどしたら再び起動します。

$ sudo /etc/init.d/racoon start

両方のサーバでracoonが起動した事を確認します。 次にsetkeyでSPDを更新します。

$ sudo /etc/init.d/setkey restart

これで無事にIPv4, IPv6ともにtransport modeでIPsecを動かす事ができました。 たぶん/etc/ipsec-tools.confでAHを有効したり、無効にしたり編集している間におかしくなったんだと思います。

それにしても元々の問題はpmtuが正常に動けば問題ないはずなのですが、この直結に近い状態でもmtuの高い側から低い側へ接続すると比較的データ量の多そうな通信の応答がなくなってしまいます。 これはVMWare上でも同様の動きをしています。 tcpdumpでみるとARPやらICMPv6 Neighbor Discoveryには正常に反応しているので、接続は切れずにこのまま画面が固まり続けるんでしょうね。

試しにこの状態で$ sudo setkey -F$ sudo setkey -FPを実行すると、反応は戻ってきました。 /etc/init.d/setkeyをリスタートしてみると、やはりまた元に戻ってしまいます…。 う〜ん、困ったなぁ…。

racoonプロセスの稼働状況

sshで相手方に接続している最中にどちらかのracoonプロセスを再起動すると、次のログのようにSADが再構成されるため、その間は通信ができなくなります。 切断はしませんでしたが、入力した文字はバッファに入ったまま応答がなくなってしまうので、使い方によってはクリティカルかもしれません。

/var/log/daemon.logから抜粋

...
Feb 28 13:38:16 localhost racoon: INFO: respond new phase 2 negotiation: 192.168.10.yy[500]<=>192.168.1.xx[500]
Feb 28 13:38:16 localhost racoon: INFO: Update the generated policy : 192.168.1.xx/32[500] 192.168.10.yy/32[500] proto=any dir=in
Feb 28 13:38:16 localhost racoon: INFO: IPsec-SA established: ESP/Transport 192.168.1.xx[0]->192.168.10.yy[0] spi=250681865(0xef11a09)
Feb 28 13:38:16 localhost racoon: INFO: IPsec-SA established: ESP/Transport 192.168.10.yy[500]->192.168.1.xx[500] spi=221824184(0xd38c4b8)
Feb 28 13:38:16 localhost racoon: ERROR: such policy does not already exist: "192.168.1.xx/32[500] 192.168.10.yy/32[500] proto=any dir=in"
Feb 28 13:38:16 localhost racoon: ERROR: such policy does not already exist: "192.168.10.yy/32[500] 192.168.1.xx/32[500] proto=any dir=out"
...

2010/02/18

OpenVPN on alixによる外出先から自宅へのアクセス

alixで稼働しているブロードバンドルータでOpenVPNサーバを稼働させてみました。 外出先からWindows XPが動くThinkpad x22とWillcom(PHS)のAX420Sを使って自宅マシンにアクセスできています。

実際のところはVNC+sshのポートフォワードでも家PCのデスクトップを操作する事ができます。 パスワード認証を止めてしまえば、SSL/TLS-VPNと同程度のセキュリティは確保できるでしょう。 ただ家の中に複数のサーバがあってSVNリポジトリへのコミットやら、直接ノートPCからアクセスできた方が便利な状況になったのでVPNを使う事にしました。

最近はIPv6も使っていますが、まだOpenVPNでのIPv6サポートは限定的なようです。 試しにIPv6アドレスで接続してみましたが、認証がちゃんと通りませんでした。 そこで今回は固定IPv4アドレスを使っています。

作業の概要

いまのところサンプルの設定ファイルを使って、共有鍵(static.key)を使った接続ができています。 「共有鍵方式は簡単!」とドキュメントにありますが、慣れの問題でデメリットを上回るほどのものではないと思いました。

このままではクライアントは1台だけしかサポートできないので、最近繰り返している手順を使って公開鍵方式を使った接続に変更します。

サーバ側の設定変更

サーバはalixで動くdebian lennyです。 パッケージからopenvpnを入れて、/etc/openvpn/server.confを編集しました。

secretで指定した共有鍵を指定している1行を削除して、tls-server, ca, cert, keyの4行を追加しました。

/etc/openvpn/server.confファイル

tls-server
dev tun
ifconfig 192.168.20.5 192.168.20.6
dh dh2048.pem
ca cacert.pem
cert newcert.pem
key newkey.pem
port 1194
user nobody
group nogroup
comp-lzo
ping 15
ping-restart 45
ping-timer-rem
persist-tun
persist-key
verb 3

あとは*.pemファイルを/etc/openvpnに配置して、パーミッションを適切にする事ぐらいでしょうか。とはいえrootだけが閲覧できるようにするのはnewkey.pemぐらいでしょう。

server.confにtls-serverを含めずに、/etc/default/openvpnを使う事もできます。

OPTARGS="--tls-server"

ただ設定個所が増えるのは良くないので、server.confにまとめるのが良いと思います。

クライアント側の設定

クライアントはThinkpad x22に入っているWindows XP SP3です。

OpenVPNが導入されているフォルダの中にcacert.pem, newkey.pem, newcert.pemをコピーします。

C:Program Files/OpenVPN/configフォルダ以下

cacert.pem
client.ovpn
newcert.pem
newkey.pem
opensvn_static.key

続いて、client.ovpnファイルを編集します。secretの1行を削除して、先頭にtls-clientを加え、他にca, cert, keyの合計4行を追加しました。

client.ovpnファイル全体

tls-client
dev tun
proto udp
nobind
persist-key
persist-tun
comp-lzo
verb 3
port 1194
remote 219.xxx.xxx.xxx
ifconfig 192.168.20.6 192.168.20.5
ca cacert.pem
cert newcert.pem
key newkey.pem
keepalive 10 60
ping-timer-rem
route 192.168.20.0 255.255.255.0
route 192.168.10.0 255.255.255.0
route 192.168.1.0 255.255.255.0

remote行に指定するのは、ブロードバンドルータのalixにつながる固定IPv4アドレスです。 そこから繋がっているサブネットに対してルーティングするため、複数のroute行を最後に書いています。

クライアントのセキュリティ

正しく設定を終えれば、Windows上ではOpenVPN Clientを起動するだけでVPNが確立します。 ノートPCが盗まれたりした場合には、少しばかり心配になってきます。

今回はnewkey.pemにパスフレーズをつけておくのがお勧めです。 newkey.pemを作成する時に"-nodes"オプションを指定していてパスフレーズを付けていなかった場合には、次のように対応できます。

$ openssl rsa -in newkey.pem -out newkey_with_pass.pem -des3

ここで"newkey.pem"が既に存在しているパスフレーズのついていない秘密鍵ファイル、"newkey_with_pass.pem"が新たに作成されるパスフレーズのついた秘密鍵ファイルで、秘密鍵としての機能自体は同じです。

Ubuntu 8.04 LTS + Firefox-3.6で英語検索ページが表示されてしまう

http://ppa.launchpad.net/mozillateam/firefox-stable/ubuntu経由でfirefox-3.6を導入しています。 この状態でGoogleToolbarの検索ボックスからGoogle検索を行なうとhl=enが設定されてしまい、日本語の検索結果ページが表示されません。

どうやらFAQらしく、GoogleのForumに回答がありました。

けっきょくはabout:config経由で、"general.useragent.locale"を"en-GB"から"ja-JP"にしたところ問題なく動いています。

この対応自体は間違っていないと思うんですけれど、エンドユーザが変更するには怖い設定方法ですよね。 これってAdd-Onの作り方とか考えて、どうあるべきだったんだろう。

2010/02/17

freeradiusでのCRLのテスト

せっかく導入したEAP-TLSですが、鍵ファイルが一人歩きしないように再発行の仕組みが必要です。

新しくクライアント用に鍵ファイルを作成する事は手順の繰り返しなので簡単ですが、古い鍵ファイルが使われないようにするためにCertificate Revocation List(CRL)ファイルをfreeradiusで使うようにしました。

CA局側での操作

作業自体は他のCA局でも可能ですが、ここでは鍵ファイルを発行したCA局で作業を行なっています。

現在無線LANのクライアント認証に使っている鍵ファイルを"demoCA/newcerts"ディレクトリから探してきて失効処理を行ないます。

公開鍵ファイルはどこにあっても良いはずですが、作成した鍵一式は既にPCに移動してしまったので、このファイルを使いました。

$ openssl ca -revoke demoCA/newcerts/D5F1DF1134CF2B80.pem

revoke時の画面出力

Using configuration from /usr/lib/ssl/openssl.cnf
Enter pass phrase for ./demoCA/private/cakey.pem:
Revoking Certificate D5F1DF1134CF2B80.
Data Base Updated
CRLファイルの作成

失効した鍵情報を含むCRLファイルを作成します。

$ openssl ca -gencrl -out demoCA/crl/crl.20100217.pem
作成したファイルの転送

CRLファイルをfreeradiusが稼働するサーバに転送します。

$ scp demoCA/crl/crl.20100217.pem 192.168.10.10:

freeradiusサーバ側での作業

鍵ファイルの配置

freeradiusサーバの/etc/freeradius/certs/crlディレクトリを作成します。 ここでcacert.pemとcrl.20100217.pemファイルの2つをコピーしてきます。

$ sudo mkdir /etc/freeradius/certs/crl
$ sudo cp ~/crl.20100217.pem /etc/freeradius/certs/crl/
$ sudo cp /etc/freeradius/certs/cacert.pem /etc/freeradius/certs/crl/

ファイルを配置したらhashを作り直します。

$ sudo c_rehash /etc/freeradius/certs/crl/

c_rehashコマンド実行時の画面出力

Doing /etc/freeradius/certs/crl/
cacert.pem => 22f12cbd.0
crl.20100217.pem => 22f12cbd.r0

c_rehashコマンド実行後のディレクトリの様子

$ sudo ls /etc/freeradius/certs/crl/
22f12cbd.0  22f12cbd.r0  cacert.pem  crl.20100217.pem
eap.conf設定ファイルの編集

eap.confを編集して、CRLを有効にします。

--- /etc/freeradius/eap.conf.20100217	2010-02-16 15:55:18.000000000 +0900
+++ /etc/freeradius/eap.conf	2010-02-17 11:37:41.000000000 +0900
@@ -17,6 +17,8 @@
                 include_length = yes
                 check_cert_cn = %{User-Name}
                 cipher_list = "DEFAULT"
+		check_crl = yes
+		CA_path = ${certdir}/crl
         }
 } 

この後で、freeradiusを再起動すると設定が有効になり、いままで使っていた鍵ファイルが使えなくなりました。

さいごに

crlの運用について

今回は日付を付けてCRLファイルを出力しましたが、Revocation DateがCRLファイルに記録されているので、必要なら配布先のradiusなどのサーバ側で保存すれば良いのかなと感じました。

ただCRLファイルの管理や配布をどのようにするべきなのか、まとまった文書がみつからないので、まだ迷っているところでもあります。 商用のCA局の鍵には、"CRL Distribution Points"(CDP)なるExtensionが入っていてURLでDER形式のCLRファイルを配布していたりします。

Apache2やFreeradiusなどではCDPには対応していないようです。 そもそもサーバが直接インターネットに接続している状況がありえないので、サーバ系ではCRLファイルの管理、配布について考えておく必要がありそうです。

クライアント側ではCDPに対応するのは簡単でしょうし、実装していくべきだと思います。 cacertsにLAN内部からアクセスできるURLをCDP情報に含めて配布する事で、おもしろい実験になりそうです。

demoCA内部でのcrl.pemの配置場所

openssl.cnfファイルではデフォルトとして、demoCA/crl.pemファイルが指定されています。 しかしソースコードを"ENV_CRL"をキーにgrepしてみた限りでは、特に使われている形跡はありません。

"demoCA/crlnumber"ファイルはしっかりと、扱われているんですけどねぇ…。 crlファイルは、そもそもの配置場所からして、どう扱うべきなんでしょうか。

Debian lennyでのhostapdとfreeradiusによるEAP-TLS環境の構築

AlixではXG-601を使って無線LAN APを構築していますが、stable系はドライバ未対応が原因で使えないため、開発版のhostapd-0.7.1を使っています。 いままではWPA-PSKを使った共有鍵方式で運用してきましたが、今回はEAP-TLSを試す事にします。

EAP-TLSはユーザ毎に認証用ファイルを配布する事ができるようになります。 誰がネットワークに接続したのか特定する必要がある環境ではとても便利な仕組みです。 共通鍵を使った環境では、鍵情報の流出+MACアドレス詐称が行なわれた場合、どの端末がネットワークに接続しているのか、どの経路で情報が漏洩したのか把握する事ができません。

公開鍵暗号化方式は、概念はとてもシンプルですが、使うまでの手順が面倒そうなので、あまり使われていないのではないでしょうか。 最近は市販されている家庭用無線LANルータにもRADIUS設定ができるものが多いので、ローカルなCA局とfreeradiusの運用から始めても良いかもしれません。

試行錯誤したところhostapd内蔵のRADIUS機能がどうしても使えなかったため、freeradiusを稼働させました。 debパッケージから構成できれば楽だったのですが、freeradiusのdebian lennyのパッケージでは、libsslライブラリとリンクされていないため、パッケージのビルドも行なっています。 詳細な手順はWPA2 + FreeRADIUS + EAP-TLSを確認してください。

参考にした文書、サイト

目次

作業全体の流れ

EAP-TLSを稼働させるまでの流れは、参考にした上記リンク先に従って進めています。 リンク先に書かれている事は省略していますが、気になったところや判りずらいところを補足する形で書きたいと思います。

  • hostapd-0.7.1のコンパイル
  • freeradiusパッケージの作成 & インストール
  • hostapd及びfreeradiusの設定
  • Windows Vistaからの接続確認

hostapd-0.7.1のコンパイル

本家からtarアーカイブを取得して、適当な場所にファイルを展開します。 configureスクリプトは附属していないので、次の手順で.configファイルを準備してコンパイルしましょう。

いまどきconfigureを使わずに.configファイルなんてlinux kernelのコンパイルみたいですね。

$ cd hostapd-0.7.1/hostapd/
$ cp defconfig .config
$ make
.configファイル

現状使っているhostapdをコンパイルした時の設定ファイルから、コメントと空行を除いたものは次のようになります。

.configファイルからコメントと空行を除いた中身

CONFIG_DRIVER_NL80211=y
CONFIG_EAP=y
CONFIG_EAP_TLS=y
CONFIG_IPV6=y

freeradiusパッケージの作成 & インストール

標準的にapt-getで導入できるfreeradiusではEAP-TLSが使えないので、設定を少し変更してパッケージを独自に作成する必要があります。

手順は参考にしたサイトにある通りですが、EAP-TLS以外は必要ないので、"--with-rlm_eap_tls"と"--with-openssl"の2個所だけを変更しました。 debian/ruleファイルの差分は次のようになっています。

debian/ruleファイルの差分

--- debian/rules.old	2010-02-15 20:17:09.000000000 +0900
+++ debian/rules	2010-02-15 20:16:36.000000000 +0900
@@ -80,14 +80,14 @@
 		--with-large-files --with-udpfromto --with-edir \
 		--enable-developer \
 		--config-cache \
-		--without-rlm_eap_tls \
+		--with-rlm_eap_tls \
 		--without-rlm_eap_ttls \
 		--without-rlm_eap_peap \
 		--without-rlm_eap_tnc \
 		--without-rlm_otp \
 		--with-rlm_sql_postgresql_lib_dir=`pg_config --libdir` \
 		--with-rlm_sql_postgresql_include_dir=`pg_config --includedir` \
-		--without-openssl \
+		--with-openssl \
 		--without-rlm_eap_ikev2 \
 		--without-rlm_sql_oracle \
 		--without-rlm_sql_unixodbc \
@@ -176,12 +176,12 @@
 	dh_strip -a --dbg-package=freeradius-dbg
 
 	dh_makeshlibs -a -n
-	for pkg in ${pkgs} ; do \
-	  if dh_shlibdeps -p $$pkg -- -O 2>/dev/null | grep -q libssl; then \
-	    echo "$$pkg links to openssl" ;\
-	    exit 1 ;\
-	  fi ;\
-	done
+#	for pkg in ${pkgs} ; do \
+#	  if dh_shlibdeps -p $$pkg -- -O 2>/dev/null | grep -q libssl; then \
+#	    echo "$$pkg links to openssl" ;\
+#	    exit 1 ;\
+#	  fi ;\
+#	done
 	dh_shlibdeps
 
 binary-common:

あとは一般ユーザでパッケージ作成のコマンドを実行して、待つだけです。

$ dpkg-buildpackage -rfakeroot
freeradiusパッケージの導入

ひとつ上(..)のディレクトリにパッケージが作成されているので、これを導入します。

$ cd ..
$ sudo dpkg -i freeradius_2.0.4+dfsg-6_i386.deb
$ sudo dpkg -i libfreeradius2_2.0.4+dfsg-6_i386.deb
$ sudo dpkg -i freeradius-common_2.0.4+dfsg-6_all.deb

ここで事前にパッケージのfreeradiusを導入していなかったり、状況によっては/etc/freeradiusに正しく設定ファイルが配置されないようです。

dpkgを実行した直後の/etc/freeradiusの様子


acct_users.dpkg-new		    otp.conf.dpkg-new
attrs.access_reject.dpkg-new	    policy.conf.dpkg-new
...

どうも依存関係が十分ではなかったようで、他のパッケージを導入しようとしたタイミングで気がつきました。

他のパッケージを導入する際に表示されたメッセージ

The following packages have unmet dependencies:
  freeradius: Depends: libperl5.10 (>= 5.10.0) but it is not going to be installed
              Depends: libsnmp15 (>= 5.4.1~dfsg) but it is not going to be installed
              Recommends: freeradius-utils but it is not going to be installed
E: Unmet dependencies. Try 'apt-get -f install' with no packages (or specify a solution).

ここに書かれているようにapt-getを実行すれば、問題は解決します。

$ sudo apt-get -f install

予防策としては事前にopensslに対応していないリポジトリのfreeradiusを導入してから、コンパイルしたfreeradiusを導入するのが良いのかもしれません。

2010/03/06追記:
どうやら設定ファイルが正しく配置されない状況があるようなので、このセクションを追加しました。

dpkg --set-selectionsオプションについて

参考にしたサイトでは、自動的にapt-getでfreeradiusのパッケージが更新されてしまう事のないように各パッケージをholdにする手順が書かれています。

しかしこの中の一部の文字コードがASCIIではないために、コマンドの実行に失敗してしまいます。

エラー時の画面出力

$ echo “freeradius hold” | sudo dpkg --set-selections
dpkg: illegal package name at line 1: must start with an alphanumeric

バイトの並びをみると"0xe2 0x80 0x9c"と"..0x9d"になるので、HTMLでいうところの&ldquo;(left double quotation mark)と&rdquo;(right double quotation mark)のようです。

ユーロ圏では母国語用キーボードで、ASCIIではない事を意識せずにleft/right double quotation markを簡単に使えるのかもしれません。 実際のところechoコマンドでのquotationは、複数の空白を含めたい場合を除けば必要ありません。

$ echo freeradius hold | sudo dpkg --set-selections
$ echo libfreeradius2 hold | sudo dpkg --set-selections
$ echo freeradius-common hold | sudo dpkg --set-selections
間違ってmakeコマンドを実行してしまったら…

この手順通りに進めれば問題ないのですが、どこかのタイミングでmakeコマンドを打ってしまうとdpkg-buildpackage -rfakerootを実行してもパッケージが作成できなくなってしまいます。 やり直そうとしてmake distcleanとかは実行してはいけないルールのようです。

設定を少し変更してパッケージの作り直しをする時には、そのまま繰り返しdpkg-buildpackageコマンドを実行すればOKです。 どうしようもなくなったら、apt-get sourceからやり直しましょう。

freeradiusの起動

2010/02/17追記:
たまたまfreeradiusが起動しなかったと思ったら、どうもバグだと気がついたのでセクシュンを追加。

正しく設定ファイルを作成しても、現在のstableパッケージ(freeradius_2.0.4+dfsg-6)に含まれる/etc/init.d/freeradiusスクリプトからはfreeradiusは起動しません。

これはバグで/var/run/freeradiusディレクトリにfreeradユーザで書き込みができないからです。 起動スクリプトはこのディレクトリがない場合には作成して、適切な権限に設定してくれますが、このディレクトリはfreeradiusパッケージの管理下にあるため最初からroot権限で作成されてしまいます。

既にrootでのみ書き込みができるディレクトリが存在するため、ディレクトリの作成が行なわれず、後続の権限修正もまた行なわれないままになるという仕組みです。

このバグはfreeradius (2.1.7+dfsg-1)で修正されている模様です。 将来のstableパッケージでは問題は起きないでしょう。

$ sudo chown freerad:freerad /var/run/freeradius

hostapd及びfreeradiusの設定

今回の設定のポイントはTLS用の鍵ファイルを作成するところで、clientAuthserverAuthを指定します。 参考にしたサイトにはchallengeパスワードを設定するよう書かれていますが、EAPTLS.pdfでは言及されていません。

デフォルトのopenssl.cnfファイルにはreq_attributesセクションが定義されているので、おそらくこれが読み込まれているようにみえます。残念ながらドキュメントを追い掛けても、ここら辺の動きについての資料をみつける事ができませんでした。

作業の概要

今回は前回のApache2のSSL化で作成したCA局の鍵をそのまま使います。

ただ後続の作業で"--extfile"オプションを使うため設定ファイルを準備しました。 これはopenssl.cnfに含める事はできないため、別ファイルである事が必要です。ファイル名や配置場所は任意で構いません。

demoCA/xpextensionsファイル

[xpclient_ext]
extendedKeyUsage=1.3.6.1.5.5.7.3.2

[xpserver_ext]
extendedKeyUsage=1.3.6.1.5.5.7.3.1

今回は同様の内容を持つ、次のファイルを使用しました。

demoCA/xpextensionsファイル ver.2

[xpclient_ext]
extendedKeyUsage=clientAuth

[xpserver_ext]
extendedKeyUsage=serverAuth

どちらのファイルを使っても、問題なく動きました。

server用鍵の生成

このステップは基本的にapache2のSSL化の時と同じですが、前回使用したコマンドラインの後ろにextensions関連のオプションを追加したところエラーになってしまいました。

エラーになる実行例

$ openssl ca -policy policy_anything -out newcert.pem -infiles newreq.pem -extensions xpserver_ext -extfile demoCA/xpextensions

今回は次の手順で鍵ファイルを作成しました。

$ openssl req -new -nodes -keyout newkey.pem -out newreq.pem -days 365
$ openssl ca -policy policy_anything -out newcert.pem -extensions xpserver_ext -extfile demoCA/xpextensions -infiles newreq.pem

Extended Key Usageの追加を確認する

        X509v3 extensions:
            X509v3 Extended Key Usage: 
                TLS Web Server Authentication
Certificate is to be certified until Feb 16 23:14:16 2011 GMT (365 days)
Sign the certificate? [y/n]:y
...

これで、サーバ用のnewkey.pem(秘密鍵)とnewcert.pem(公開鍵)のペアが作成できているはずです。 別のディレクトリを作成して鍵ファイルを保存します。

$ mkdir wireless_server_keys
$ mv newcert.pem newkey.pem wireless_server_keys/
$ cp demoCA/cacert.pem wireless_server_keys/

2010/03/06追記:
中間CAを使う時にはfreeradiusに指定するcacert.pemファイルは1つなので、親CAと中間CAのpemファイルを連結します。
$ cat ../demoCA/cacert.pem demoCA/cacert.pem > wireless_server_keys/cacert.pem

CN(CommonName)にはローカルのDNSサーバに登録されているFQDNを指定しました。

ここまでの作業が無事に終ったら、秘密鍵(newkey.pem)に繋がるnewreq.pemファイルは削除しておきます。

$ rm newreq.pem
client用の鍵生成

手順自体は同じですが、Windowsでクライアント用の鍵ファイルを読み込むためにpkcs12形式のファイルも作成しておきます。 参考にした手順では、この時に"-clcerts"オプションを指定してCA局の公開鍵を別経路で取り込むようにしています。

セキュリティ的にはより良い方法ですし、もし社内や閉じられたコミュニティ向けのローカルなCA局を運用するのであれば、別経路でのCA局公開鍵の配布は必須とは重います。 ここではpkcs12にCA局の情報も入れています。

$ openssl req -new -nodes -keyout newkey.pem -out newreq.pem -days 365
$ openssl ca -policy policy_anything -out newcert.pem -extensions xpclient_ext -extfile demoCA/xpextensions -infiles newreq.pem 
$ openssl pkcs12 -in newcert.pem -inkey newkey.pem -certfile ./demoCA/cacert.pem -out newcert.p12 -export -name "My Certificate"

ここでのCNにはメールアドレスを入力しました。 社員番号のようなユニークなキーがあれば、それでも良いと思います。

2番目のコマンドで署名をする時には、次のようなExtended Key Usageが付与されているか確認します。 また最後のコマンドでnewcert.p12ファイルを作成する時には、任意のパスワードを付けます。 CA局のパスワードとは違うものにするのが良いでしょう。

Extended Key Usageの追加を確認する

        X509v3 extensions:
            X509v3 Extended Key Usage: 
                TLS Web Client Authentication
Certificate is to be certified until Feb 16 23:26:17 2011 GMT (365 days)
Sign the certificate? [y/n]:y
...

作成した鍵ファイルを別ディレクトリに保存しておきます。

$ mkdir wireless_client_keys
$ mv newcert.pem newkey.pem newcert.p12 wireless_client_keys/
$ rm newreq.pem

freeradiusサーバへの鍵ファイルの配布

TLS-EAPに対応したfreeradiusが既に導入されている事が必要です。 このfreeradiusが稼働するサーバにscpなどで鍵ファイルのディレクトリ全体をコピーしておきます。

$ scp -r wireless_server_keys 192.168.10.10:

freeradiusが稼働するサーバの/etc/freeradius/certs以下にファイルを保存していきます。

$ sudo cp wireless_server_keys/cacert.pem /etc/freeradius/certs
$ sudo cp wireless_server_keys/newkey.pem /etc/freeradius/certs
$ sudo cp wireless_server_keys/newcert.pem /etc/freeradius/certs

さらに参考にしたサイトの例に従って"dh"ファイルや"random"ファイルを作成します。 dhファイルの作成はalixには重い作業なので、より高速なCPUを積んだマシンで作成するのがよいでしょう。

$ openssl dhparam -5 -out dh1024.pem 1024
$ sudo cp dh1024.pem /etc/freeradius/certs

randomファイルはalix上で作成します。

$ sudo dd if=/dev/urandom of=/etc/freeradius/certs/random count=2

2010/03/06追記:
参考にした手順で作成した"dh"ファイルは512bitだったので、1024bitで作成しました。 2048bitでも良いのですが、ファイル作成に時間がかかるのと、そこまでは またrandomファイルの作成手順についても載せています。
この作業の結果、eap.confの中でのdhファイル名を変更(dh->dh1024.pem)しました。

またeap.confは次のようになりました。

/etc/freeradius/eap.confファイル

eap {
        default_eap_type = tls
        timer_expire     = 60
        ignore_unknown_eap_types = no
        cisco_accounting_username_bug = no         

        tls {
                certdir = ${confdir}/certs
                cadir = ${confdir}/certs
                private_key_password = 
                private_key_file = ${certdir}/newkey.pem
                certificate_file = ${certdir}/newcert.pem
                CA_file = ${cadir}/cacert.pem
                dh_file = ${certdir}/dh1024.pem
                random_file = ${certdir}/random
                fragment_size = 1024
                include_length = yes
                check_cert_cn = %{User-Name}
                cipher_list = "DEFAULT"
        }
}

次にclient.confファイルを編集します。 テストのためにclient localhostの定義がありますが、secretは適当に変更しておきます。 そして最後にhostapdが走るホストのIPアドレスかFQDNを定義します。

client 192.168.1.x {
        secret = 6a_abp1z75_5e318
        shortname = wireless-ap
}

パスワードは次のようなコマンドで適当な文字列を表示させて使っています。 このままだと0-9,a-fの範囲しか使わないので数字の'0'は適当な記号に、'f'はa-fの範囲外の適当なアルファベットに手で置き換えるといったことをしています。

$ dd if=/dev/urandom bs=16 count=1 | od -t x8

2010/03/06追記:
あっちこっち参照するのは面倒なのでclient.confの編集とパスワードの適当な選び方について追加しました。

サーバ用のnewreq.pemファイルを生成する段階で、"-nodes"オプションを指定してパスワードをつけていません。 このためprivate_key_passwordには何も指定していないところが、参考にした手順と違うところです。

hostapdの設定について

hostapd内蔵のradius機能を使わないためにeap_server=0を指定するか、コメントアウトする事が必要だろうと思います。 ただ"WPA/IEEE 802.11i configuration"セクションにあるオプションを有効にしたところ、PTK Rekeyingのタイミングで接続が切れるようになってしまいました。

できるだけデフォルト設定のように使って、wpa_ptk_rekeyなどのオプションは指定しないのが良さそうです。 いまのところhostapd.confは次のようになっています。

コメント、空行を除いたhostapd.conf

interface=wlan0
bridge=br0
driver=nl80211
logger_syslog=-1
logger_syslog_level=2
logger_stdout=-1
logger_stdout_level=0
dump_file=/tmp/hostapd.dump
ctrl_interface=/var/run/hostapd
ctrl_interface_group=0
ssid=ALIXHOMEAP4TEST
country_code=JP
ieee80211d=1
hw_mode=g
channel=9
beacon_int=100
dtim_period=2
max_num_sta=255
rts_threshold=2347
fragm_threshold=2346
macaddr_acl=0
auth_algs=1
ignore_broadcast_ssid=0
wmm_enabled=1
wmm_ac_bk_cwmin=4
wmm_ac_bk_cwmax=10
wmm_ac_bk_aifs=7
wmm_ac_bk_txop_limit=0
wmm_ac_bk_acm=0
wmm_ac_be_aifs=3
wmm_ac_be_cwmin=4
wmm_ac_be_cwmax=10
wmm_ac_be_txop_limit=0
wmm_ac_be_acm=0
wmm_ac_vi_aifs=2
wmm_ac_vi_cwmin=3
wmm_ac_vi_cwmax=4
wmm_ac_vi_txop_limit=94
wmm_ac_vi_acm=0
wmm_ac_vo_aifs=2
wmm_ac_vo_cwmin=2
wmm_ac_vo_cwmax=3
wmm_ac_vo_txop_limit=47
wmm_ac_vo_acm=0
ap_max_inactivity=300
max_listen_interval=100
ieee8021x=1
eapol_version=1
eapol_key_index_workaround=1
eap_reauth_period=1800
use_pae_group_addr=0
eap_server=0
own_ip_addr=192.168.10.10
nas_identifier=alix.example.org
auth_server_addr=192.168.10.10
auth_server_port=1812
auth_server_shared_secret=xxxxxxxxxxxxxxxxxx
acct_server_addr=192.168.10.10
acct_server_port=1813
acct_server_shared_secret=xxxxxxxxxxxxxxxxxx
radius_retry_primary_interval=600
radius_acct_interim_interval=1200
dynamic_vlan=0
wpa=1
wpa_key_mgmt=WPA-EAP
wpa_pairwise=TKIP

Windows Vistaからの接続確認

作成したnewcert.p12ファイルをUSBメモリなどでコピーし、このファイルをWindows上でダブルクリックするとインストールが始まります。 気をつけるところはデフォルトではどのCA局も信頼する対象になっていませんから、Access Pointのプロパティから信頼するCA局としてチェックを入れるところでしょうか。

ただ、wpa_ptk_rekeyをコメントアウトして、比較的安定して動いていると思いますが、WPA-PSKの時と比べると、まだ時々切断されている事があります。

さいごに

hostapdでEAP-TLSを実現するための方法をみると、CA局を専用に作らないとなのかなぁ、などいろいろ思いますが、なんとかApache2のSSL化で使ったdemoCA/cacert.pemが使えました。 ただ、ちゃんと動く環境を手に入れるまでは不確定な要素を排除するために、専用のdemoCA/cacert.pemを作る確実な手順を踏襲するのが良いとは思います。

いくつかの手順をみるとそれぞれの工夫がみて取れます。 EAPTLS.pdfではスクリプトが準備されていますが、内部を理解しないとちゃんと動かない、なんてことにもなりそうです。 TLS周りの作業は自分でスクリプト化するのは良い事ですが、人が作成したスクリプトは時間をかけても確実に各ステップを確認して、自分なりのスクリプトに編み直す事が必要そうです。

hostapdの内蔵TLSがうまく動いていないとか、未解決のままにしている事はありますが、ひとまずは目的を達成できたので情報を整理しておこうと思います。

2010/02/16

Ubuntu 8.04 LTSでGoogle ChromeやChromiumがフリーズする

Firefoxと平行してGoogle ChromeのBeta版やnightly-build版のChromium Browserの64bit版を使っています。

しかし今年に入ってしばらくは問題なかったのですが、いつからか、Flashを使ったページを閲覧すると画面が正しく描画されずに必ずフリーズし、エラーになるようになってしまいました。

以前は"--enable-plugins"オプションをつければ、~/.mozilla/plugins/libflashplayer.soを自動的に読み込んでくれたのですが、最近は挙動が少し違うようです。

実行時に表示されたエラー

$ chromium-browser 
[20868:20875:57715401154:ERROR:base/file_util_posix.cc(661)] Couldn't stat /usr/lib/firefox/plugins/flashplugin-alternative.so: No such file or directory
GCJ PLUGIN: thread 0x2bfc220: NP_GetMIMEDescription
GCJ PLUGIN: thread 0x2bfc220: NP_GetMIMEDescription return
...

最近はlabs.adobe.comから直接最新の64bit版なflashplayerを導入していますが、どうやら以前導入していたパッケージが作った古いシンボリックリンクに引きずられているような感じです。

とりあえずの対応

古いシンボリックリンクの削除

いくつか対応する必要があって、まずは/usr/lib/firefox/plugins/flashplugin-alternative.soのようなリンク先が存在していないシンボリックリンクの削除をしました。

chromium的な対応方法

次にchromium用のpluginsフォルダにlibflashplayer.soファイルをコピーしました。

$ sudo cp libflashplayer.so /usr/lib/chromium-browser/plugins/
google-chrome的な対応方法

google-chrome用にはpluginsフォルダを準備してから、libflashplayer.soファイルをコピーします。

$ sudo mkdir /opt/google/chrome/plugins
$ sudo cp libflashplayer.so /opt/google/chrome/plugins

とりあえず"--enable-plugins"をつけさえすれば、無事にflashを含むWebサイトもみれるようになりました。

2010/02/15

突然USBフラッシュメモリが使えなくなる

alixのバックアップ用に使ってきたELECOM製のMF-AU2 USBフラッシュメモリが壊れてしまいました。 とはいっても読み出しについてはエラーは出ていません。

ちょっと普通じゃない使い方

このフラッシュメモリはext3フォーマットをした上で、DNSサーバにしているalixのバックアップ領域として使っていました。

バックアップ自体はpdumpfsで行なうためハードリンクが使いたかったので、ext3にしていたわけです。

alix+debianの組み合せだと、起動時にUSBメモリが認識されるタイミングが一定ではなく、使いたい時にマウントしたかったのがautofsにした理由です。

ちなみに設定ファイルは次のようにしていました。

/etc/auto.masterファイル抜粋

/misc	/etc/auto.misc

/etc/auto.miscファイル抜粋

bk		-fstype=ext3,noatime		:/dev/disk/by-uuid/d83cc93b-a40e-4435-8de6-d18a56491f66

今回の症状

たまたまログインしてバックアップの様子を確認した時に発見しました。 家にもちゃんとnagios辺りで監視系を作らないとだめかなぁ。

新しくディレクトリを作ろうとすると次のようなメッセージが表示されます。

エラーメッセージ

$ sudo mkdir test
mkdir: ディレクトリ `test' を作成できません: No space left on device

とはいえ、領域を全部使い切ったわけでもなく、まだ半分程度は余っています。

現時点での容量

/dev/sdh1              3886448   1787812   1901212  49% /media/disk

常に100%を使い切るような使い方をすれば、あっという間に使えなくなる事は想像できたのですが、まだ51%残っている状態で、簡単に使えなくなり少し驚いています。

別の格安フラッシュメモリをチェックしてみる

念のため昨年末に近くの家電屋さんで放出されていたKingston製の格安メモリもチェックしてみると、みごとに不具合が出ていました。 ログの日付をみると2009/12/24のクリスマス前後から使い出したばかりみたいなんだけどなぁ…。

格安フラッシュメモリ領域にlsした時のエラー

$ ls /misc/bk
ls: cannot access /misc/bk/2009: Input/output error
2009  2010  OLD  do_pdumpfs.sh  ignore.list  latest  lost+found  stderr.log  stdout.log

この状態だと2009ディレクトリ以下のファイル、ディレクトリにはアクセスできません。 まぁこっちはまだ書き込めているので、しばらくは使い続けてダメになってから交換しようと思います。

復旧までのステップ

この領域はバックアップ領域なので、簡単に復旧できるのが良いところです。

  • 新しいUSBメモリを装着
  • ext3フォーマット
  • tune2fsでUUIDを調べて、/etc/auto.miscを書き換え
  • do_pdumpfs.shスクリプトを配置する

あとはcrontabから/misc/bk/do_pdumpfs.shが自動的に呼ばれて終りです。 autofsをベースにしたpdumpfsとcrontabの組み合せはとてもお手軽で助かっています。

問題は稼働状況の把握だけですね。

さいごに

まぁバックアップ領域なので、また適当なフラッシュメモリを買ってくるつもりです。NANDメモリの製造元でもあるSANDISK製のものは価格のわりに速度も品質も安定していて好きだったんですけどね。最近はもう売られていません。

SONY製のハイスピードタイプは読み出しは早いですけれど、全体的なパフォーマンスは価格ほどではないですし、どれもこれも信頼性という意味ではイマイチな感じなんですよね。

とはいえ「今のところ」信頼している製品でもNAND MLCである限りは潜在的に問題があるのは間違いありません。 繰り返しになりますが、USBフラッシュメモリをext3フォーマットして使うことに問題があると思います。今回のように純増していく用途であれば、NILFSが最適でしょうね。でもNILFSはハードリンクに対応しているのかなぁ、今度試してみましょう。

ちゃんと使いたいならお金を出して10万回以上の書き込み/消去サイクルを保証しているTranscendのJetFlash 1xxシリーズを使うべきです。 仕事で使うなら、このクラスの製品がお勧めです。

2010/02/13

Ubuntu 8.04 LTS上のApache2をSSL化

いまさらですが、デスクトップで作業ログを取っているhiki用のapacheサーバのSSL化を試みてみました。

参考にしたドキュメント

具体的にピンポイントで参考になったところはないのですが、以下のようなサイトを参考にしました。

はじめに

opensslを使う点ではどのドキュメントも同じなのですが、その中での手順は断定的に書かれているものも少なくありません。 しかし実際には環境に依存しているため、そのままの手順では使えないのではないかと感じました。 またドキュメントが書かれた時期によっても、作法に違いがあるようです。

今回はCA.plを使用し、できるだけ環境に依存しないように心がけましたが、徹底できてはいないと思います。 しかし、/etc/ssl/openssl.cnfファイルはデフォルトのままにして書き換えていません。

CA局の準備

この作業では次のファイルを準備する事が目標です。

  • demoCA/cacert.pem
  • demoCA/private/cakey.pem
$ /usr/lib/ssl/misc/CA.pl -newca
内部で実行されているopensslコマンド

CA.plスクリプトの内部ではOPENSSL環境変数でopensslコマンドを指定する事が可能です。通常は複数バージョンがインストールされた環境などを意識していると思うのですが、今回はこれを流用してみようと思います。

$ env OPENSSL="echo $ openssl" /usr/lib/ssl/misc/CA.pl -newca

CA.pl -newcaが発行するopensslコマンド行

$ openssl req -new -keyout ./demoCA/private/cakey.pem -out ./demoCA/careq.pem
$ openssl ca -create_serial -out ./demoCA/cacert.pem -days 1095 -batch -keyfile ./demoCA/private/cakey.pem -selfsign -extensions v3_ca -infiles ./demoCA/careq.pem

CA局の期限のデフォルト(3年)を延長するために、/etc/ssl/openssl.cnfファイルを書き換えるようアドバイスしているサイトもあります。 直接コマンドを打てばその必要はなく、-days 3650のように適当な日数を指定する事で可能です。

また作業の最後で間違ったPEMパスフレーズを入力するなどして、CA.pl -newcaの実行に失敗した場合には、既にdemoCAディレクトリが存在するため、やり直しができません。そんな場合でも、最後の1行を実行することで準備したcareq.pemを活かすことができます。

Apacheサーバ用のSSL鍵ファイルの準備

今回の作業では次のファイルを作成します。 念のためファイル名は任意なので、適当に読み替えても問題ありません。

  • server_key.pem
  • server_cert.pem
前準備:CA.plコマンドの実行

作業は引き続き、"demoCA"ディレクトリがみえるディレクトリで行ないます。

$ /usr/lib/ssl/misc/CA.pl -newreq
$ /usr/lib/ssl/misc/CA.pl -sign

一連の作業ではCA局と似たような情報を入力しますが、common nameをサーバのFQDNにする以外は基本的に何を入力しても問題にはなりません。CA局と同じように入力しても良いですし、まったく違うドメインの情報でも良いです。

具体的な作業時の注意点は次のようなところでしょうか。

  • "-newreq"時に入力するPEMキーのパスフレーズはなんでも良いですが、覚えておいてください。
  • "-sign"時の最後に入力を求められるPEMキーのパスフレーズは、最初に作成したCA局のものです。

この作業を終えた段階では、まだ目的のファイルはありません。 次でサーバの秘密鍵"newkey.pem"に設定されているパスフレーズがなくても、秘密鍵の情報がみえるようにします。

ファイルの作成
$ openssl rsa < newkey.pem > server_key.pem

ここで入力するパスフレーズは、先ほど「覚えておいて」と書いた"CA.pl -newreq"実行時に入力したものです。

サーバが起動時に秘密鍵を読み込めるようにserver_key.pemにはパスフレーズの設定されていませんから、必要のない人には公開しないようにします。もともと秘密鍵はそういうものですけどね。

次にserver_cert.pemを準備します。これはそのまま。

$ cp newcert.pem server_cert.pem

ここまでの作業で2つの鍵ファイルが準備できたので、このファイルをapache2サーバに組み込んでいきます。

内部でのopensslの実行

先程と同じように、CA.plの内部で発行されたopensslコマンドを確認する事ができます。

CA.pl -newreqコマンド実行時に呼ばれるopensslコマンドとメッセージ

$ openssl req -new -keyout newkey.pem -out newreq.pem -days 365
Request is in newreq.pem, private key is in newkey.pem

CA.pl -signコマンド実行時に呼ばれるopensslコマンドとメッセージ

$ openssl ca -policy policy_anything -out newcert.pem -infiles newreq.pem
Signed certificate is in newcert.pem

この2つの出力から、newreq.pem, newkey.pem, newcert.pemの3つのファイルが作成される事がわかります。 またサーバの秘密鍵の寿命を延したければ、/etc/ssl/openssl.cnfを書き換えてもよいですが、コマンドラインで-daysに続いて任意の日数を設定する事も可能です。

ただし、ここでcakey.pemを指定せずにCSRファイルに署名されるのは、/etc/ssl/openssl.cnfファイルにprivate_key = $dir/private/cakey.pemという一行が入っているからです。

apache2用設定ファイルの編集

mod_ssl自体はapache2.2-commonに含まれているので、apache2サーバが起動していれば追加の作業は限られています。

mod_sslの有効化

直接シンボリックリンクを張っても良いのですが、コマンドを使ってみます。

$ sudo a2enmod ssl

念のためシンボリックリンクファイルが存在する事を確認しておきます。

$ ls -al /etc/apache2/mods-enabled/ssl.*

コマンド出力例

lrwxrwxrwx 1 root root 26 2010-02-13 17:44 /etc/apache2/mods-enabled/ssl.conf -> ../mods-available/ssl.conf
lrwxrwxrwx 1 root root 26 2010-02-13 17:44 /etc/apache2/mods-enabled/ssl.load -> ../mods-available/ssl.load
設定ファイルの配置と準備

mod_ssl自体は有効になって、ポート443にSSL接続する事は可能になりました。 次はどんなコンテンツを返せば良いのかといった設定する必要があります。

とりあえず最低限で考えて、デフォルトの443用設定を次のようにしました。

/etc/apache2/sites-available/sslファイル


NameVirtualHost *:443
<virtualhost *:443>
        ServerAdmin webmaster@example.org

        SSLEngine On
	SSLCipherSuite RSA:!EXP:!NULL:+HIGH:-MEDIUM:-LOW
        SSLCertificateFile /etc/apache2/server_cert.pem
        SSLCertificateKeyFile /etc/apache2/server_key.pem

        DocumentRoot /var/www/
        <directory />
                Options FollowSymLinks
                AllowOverride None
        </directory>
</virtualhost>

やはり同じようにコマンドで、/etc/apache2/sites-enabled/ディレクトリ以下にシンボリックリンクを作成します。

$ sudo a2ensite ssl
鍵ファイルの配置

設定ファイルに合せてファイルを配置します。

$ cp server_cert.pem /etc/apache2
$ cp server_key.pem /etc/apache2

また勝手にサーバを立てらてしまうかもしれませんからパーミッションにも気を配っておきましょう。

$ sudo chmod 400 /etc/apache2/server_*.pem
設定の反映

最終的にはapacheのプロセスをリスタートする事で、SSL接続が可能になります。

$ sudo /etc/init.d/apache2 restart

さいごに

今回は自分の環境を守る事を考えて、少ない手順でそれなりに動く作業手順を残す事を目的にしました。 ただし具体的なパラメータについては、common name以外は触れていません。

CA.plスクリプトを使ったサイトでは具体的にどんな値を入力するのか説明されていますし、内容自体には意味がないからです。とはいえ、できるだけ現実的な値を設定するのがお勧めです。

またsslファイルの中で設定しているSSLCipherSuiteパラメータの値については、それなりに議論があると思います。明示的に設定をしていてもデフォルトのパラメータのを書いたサイトが多いようでした。

サーバ側でクライアントを選択するような設定は良くないかもしれませんが、いまさらSSLv2はないですよね。それに自分が使う事を考えると、SSLv2で接続するクライアントやライブラリがあるかもしれないので、テストを兼ねて厳しい設定にしておきました。

CA.plに限らず、フレームワークを使う場合には、裏でどんなコマンドが発行されているのかを確認しておくのがお勧めです。

2010/02/12

AlixでのIPv6接続の自動化を試してみる

Alixで作成したブロードバンドルータを再起動した時に、自動でfeel6へ接続しIPv6通信ができるようにしました。 単純にいくつかのスクリプトを作成して、組み合せただけですけどね。

修正 & 作成したスクリプトについて

今回作成したスクリプトは次の通りです。

  • /usr/local/sbin/yadtcpc.pl
  • /usr/local/sbin/ipv6_up.sh
  • /usr/local/sbin/ipv6_down.sh

起動時に呼ばれるスクリプトは"ipv6_up.sh"で、"ipv6_down.sh"は停止時、及び、メンテナンス用のスクリプト。 yadtcpc.plは"ipv6_up.sh"が内部的に呼び出すため、明示的に実行する必要はありません。

/usr/local/sbin/yadtcpc.pl

前回の投稿で紹介したNet::POP3モジュールを使ったDTCPクライアントです。少し変更して、DTCP接続時に渡された情報を標準出力ではなくファイルに出力するようにしました。 接続時には、このファイルから必要な情報を読み取るようにしています。

/var/run/yadtcpc.infoファイル

local=219.xxx.xxx.xxx,remote=43.244.xxx.xx,prefix=2001:03xx:xxxx::,mask=48

また内部では適当なwaitを入れてループしていますが、"/var/run/yadtcpc.control"ファイルから停止の指示があるとquit()により接続を切って全体の処理を中断します。 停止したい場合には$ echo exit | sudo tee /var/run/yadtcpc.controlのようなコマンドを実行します。

前回作成したpackage YADtcpc;はそのまま使い、後ろにあるmain処理全体を次のようなコードで置き換えています。

差し替え個所

...
################
## main class ##
################
package Main;

sub new {
  my ($class) = @_;
  my $self = {
    dtcpc => undef,
    local => undef,
    remote => undef,
    prefix => undef,
    mask => undef,
  
    feel6_remote => "dtcp.feel6.jp",
    feel6_port => 20200,
    feel6_id => undef,
    feel6_password => undef,
  };

  bless $self, $class;
  return $self;
}

sub connect {
  my ($self) = @_;

  if($self->{dtcpc}) {
    $self->{dtcpc}->quit();
    $self->{dtcpc} = undef;
  }
  $self->{dtcpc} = YADtcpc->new($self->{feel6_remote}, Port=>$self->{feel6_port});
  ($self->{local}, $self->{remote}, $self->{prefix},$self->{mask}) = $self->{dtcpc}->dtcp($self->{feel6_id}, $self->{feel6_password});

  $self;
}

sub write_log {
  my ($self, $log_file) = @_;
  if(open(LOG, ">$log_file")) {
    printf LOG "local=%s,remote=%s,prefix=%s,mask=%s\n", $self->{local}, $self->{remote}, $self->{prefix},$self->{mask};
    close(LOG);
  }

  $self;
}

## for debug purpose only
sub print_log {
  my ($self) = @_;
  printf "local=%s,remote=%s,prefix=%s,mask=%s\n", $self->{local}, $self->{remote}, $self->{prefix},$self->{mask};
  $self;
}
sub ping_pong {
  my ($self, $check_file) = @_;
  while ($self->{dtcpc}->ping() =~ /pong/i) {
    if(open(CHECK, $check_file)) {
      my $check = <CHECK>;
      if($check =~ /exit/i) {
        $self->{dtcpc}->quit();
        exit;
      }
      close(CHECK);
    }
    sleep 210; ## essential to send 'ping' within five minutes intervales.
  }

  $self;
}

##########
## main ##
##########
my $main = Main->new();
$main->{feel6_remote} = "dtcp.feel6.jp";
$main->{feel6_port} = 20200;
$main->{feel6_id} = "xxxxxxxx";
$main->{feel6_password} = "xxxxxxxx";

my $info_file = "/var/run/yadtcpc.info";
my $control_file = "/var/run/yadtcpc.control";
do {
  $main->connect();
  $main->write_log($info_file);
  $main->ping_pong($control_file);
  sleep 1;
} while(1);

__END__

あとはPIDを"/var/run/yadtcpc.pid"辺りに書き出して、コマンドの起動時に重複起動のチェックをしようかなと思っています。

/usr/local/sbin/ipv6_up.sh

内部では次のような処理を行なっています。

  • DTCPクライアント(yadtcpc.pl)の起動
  • /var/run/yadtcpc.infoファイルの読み込み
  • デフォルトルートの設定
  • eth1, eth2へのユニキャストアドレスの設定、及びルーティング
  • RAサーバ(radvd)の起動

/usr/local/sbin/ipv6_up.shファイル

#!/bin/bash

umask 022
PATH=/sbin:/usr/sbin:/bin:/usr/bin
LANG=C
export PATH LANG

function ipv4to6addr {
  ## main process
  ipv4_1="$(echo $1 | cut -d'.' -f1)"
  ipv4_2="$(echo $1 | cut -d'.' -f2)"
  ipv4_3="$(echo $1 | cut -d'.' -f3)"
  ipv4_4="$(echo $1 | cut -d'.' -f4)"

  printf "%02x%02x:%02x%02x\n" "${ipv4_1}" "${ipv4_2}" "${ipv4_3}" "${ipv4_4}"
}

## start the my DTCP client
## - first remove the control file
YADTCPC_FILE=/var/run/yadtcpc.control
test -f ${YADTCPC_FILE} && rm ${YADTCPC_FILE}
## - start the client
/usr/local/sbin/yadtcpc.pl &
## - waiting to finish the DTCP configuration
sleep 5

YADTCPC_FILE=/var/run/yadtcpc.info
if test -f ${YADTCPC_FILE}; then
  IPV6_LOCAL="$(cat ${YADTCPC_FILE}|cut -d, -f1|cut -d= -f2)"
  IPV6_REMOTE="$(cat ${YADTCPC_FILE}|cut -d, -f2|cut -d= -f2)"
  IPV6_PREFIX="$(cat ${YADTCPC_FILE}|cut -d, -f3|cut -d= -f2)"
  IPV6_MASK="$(cat ${YADTCPC_FILE}|cut -d, -f4|cut -d= -f2)"
else
  exit 1
fi

## setup sit device
ifconfig sit0 up tunnel ::${IPV6_REMOTE} mtu 1454
route -A inet6 del ::/0 dev eth0
DEV_NAME="$(ifconfig | grep ^sit | grep -v sit0 | head -1 | cut -d' ' -f1)"
test "${DEV_NAME}" && route -A inet6 add ::/0 dev "${DEV_NAME}"

## setup network address
DEV1_IP="$(ip -f inet addr show eth1 | tail -1 | awk '{print $2}' | cut -d/ -f1)"
DEV1_NET="$(echo ${IPV6_PREFIX}|sed -e 's/::/:/')1"
ip -f inet6 addr add ${DEV1_NET}::$(ipv4to6addr ${DEV1_IP})/64 dev eth1
ip -f inet6 route add ${DEV1_NET}::/64 dev eth1

DEV2_IP="$(ip -f inet addr show eth2 | tail -1 | awk '{print $2}' | cut -d/ -f1)"
DEV2_NET="$(echo ${IPV6_PREFIX}|sed -e 's/::/:/')10"
ip -f inet6 addr add ${DEV2_NET}::$(ipv4to6addr ${DEV2_IP})/64 dev eth2
ip -f inet6 route add ${DEV2_NET}::/64 dev eth2

## start radvd
INIT_RADVD=/etc/init.d/radvd
if test -f ${INIT_RADVD}; then
  ${INIT_RADVD} start
fi

/usr/local/sbin/ipv6_down.sh

内部の処理は、ほぼ起動時の処理の逆ですが、既にLAN内部でIPv6を使った接続済みのセッションを切断しないようにデバイスのIPv6アドレス、及び、ルーティング情報は削除していません。

/usr/local/sbin/ipv6_down.shファイル

#!/bin/bash

umask 022
LANG=C
PATH=/sbin:/bin:/usr/bin/:/usr/sbin
export LANG PATH

if test -f /var/run/yadtcpc.info; then
  IPV6_LOCAL="$(cat /var/run/yadtcpc.info|cut -d, -f1|cut -d= -f2)"
  IPV6_REMOTE="$(cat /var/run/yadtcpc.info|cut -d, -f2|cut -d= -f2)"
  IPV6_PREFIX="$(cat /var/run/yadtcpc.info|cut -d, -f3|cut -d= -f2)"
  IPV6_MASK="$(cat /var/run/yadtcpc.info|cut -d, -f4|cut -d= -f2)"
fi

## stop the my DTCP client
echo exit > /var/run/yadtcpc.control

## list up sit+ devices other than sit0
DEV_LIST="$(ifconfig | grep ^sit | grep -v sit0 | cut -d' ' -f1)"

## stop radvd
INIT_RADVD=/etc/init.d/radvd
if test -f ${INIT_RADVD}; then
  ${INIT_RADVD} stop
fi

## down devices
for sit_dev in ${DEV_LIST}
do
  route -A inet6 del ::/0 dev ${sit_dev}
  ip link set ${sit_dev} down
  ip tunnel del ${sit_dev}
done

/etc/network/interfacesファイルの修正

ファイルを配置してから、interfacesファイルを編集します。 再起動の後で$ ping6 www.kame.netが成功する事を確認しています。

interfacesファイル変更後

auto dsl-provider
iface dsl-provider inet ppp
  pre-up /sbin/ifconfig eth0 up # line maintained by pppoeconf
  provider dsl-provider
  post-up /usr/local/sbin/config_iptables.sh
  post-up /usr/local/sbin/config_ip6tables.sh
  post-up /usr/local/sbin/ipv6_up.sh
  pre-down /usr/local/sbin/ipv6_down.sh

2010/02/11

IPv6 + TCP Wrapperでのnetmaskの取り扱いについて

LAN内部での事ですが、DNSにAAAAレコードを追加し、Ipv6を使ってサーバに接続するようになりました。 しかしAlixで作成したゲートウェイにIPv6経由でSSH接続したときだけ、タイムアウトをしているかのような動きになったので原因を追ってみました。

エラーの内容::クライアント編

サーバ側に出力されたログでは、原因が直感的にはわかりませんでした。 一般的にサーバ側は誰かに管理されていて、ログをみたり設定の変更ができる事は少ないですよね。 そんなわけでsshで困った時にはクライアント側で-vオプションを使うのがお勧めです。

$ ssh -v 2001:03xx:xxxx:1::c0a8:0101

出力されたエラーの内容

OpenSSH_4.7p1 Debian-8ubuntu1.2, OpenSSL 0.9.8g 19 Oct 2007
debug1: Reading configuration data /home/yasu/.ssh/config
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: Applying options for *
debug1: Connecting to 2001:03xx:xxxx:1::c0a8:0a01 [2001:03xx:xxxx:1::c0a8:0a01] port 22.
debug1: Connection established.
debug1: identity file /home/yasu/.ssh/identity type 0
debug1: identity file /home/yasu/.ssh/id_rsa type -1
debug1: identity file /home/yasu/.ssh/id_dsa type 2
ssh_exchange_identification: Connection closed by remote host

エラーの内容::サーバ編

ちなみにサーバ側では次のようなログでした。

サーバ側の/var/log/auth.logファイル抜粋

Feb 11 18:07:56 xxxxxxxx sshd[5424]: refused connect from 2001:03xx:xxxx:1::xxxx (2001:03xx:xxxx:1::xxxx)

サーバ側で接続を拒否しているかのような動きですが、すぐには原因が思いつきませんでした。

解決編

結局のところ原因は、/etc/hosts.allowにIPv4用のTCP Wrapper(libwrap)の設定を書いていたことでした。

原因がわかればなんてこともないのですけどね。 修正内容はhosts.allowにIPv6用の設定を追加しただけなのですが、ネットマスクを使ったルールの書式が少し判りずらいかなと感じました。

追加した/etc/hosts.allowファイル

ALL: 192.168.1.0/24 192.168.10.0/24 [2001:03xx:xxxx::]/48

ブランケットの中に/48を入れてしまうとうまく動きません。

今回はiptablesの保険として設定しておいたTCP Wrapperがちゃんと仕事をしてくれた事でトラブルになってしまいました。 まぁIPv6用の設定方法もわかったし、良しとしましょう。

2010/02/10

Ubuntu 8.04 x86_64 LTSでfirefox 3.6を試してみる

Ubuntu 8.04に付属のfirefoxは3.0.17のままで、3.6の高速なレンダリングと比較すると少し物足りなく感じる場面が少なくありません。

そこで今回はfirefox 3.6が使えないか試してみる事にしました。

apt-get install時の注意

まず私が使っている環境は以前にもfirefox周りで試している事があるので、クリーンインストールした状態とは違うかもしれません。

以前の2つのビルドを試す前に導入したfirefox-3.6パッケージを削除する以外に、次のようにfirefoxパッケージも削除しておかないと問題が起こりました。

$ sudo apt-get remove firefox

Firefox 3.6 Daily-buildを試してみる

ubuntu geekのFirefox 3.6を参考に、家のメイン環境にFirefox 3.6を導入してみました。 記事は英語でも、無視して"For Ubuntu 8.04 (hardy) Users"から下に書かれている設定とコマンドをパチパチ打つだけです。

記事にはfirefox-3.6パッケージを導入していますが、ここではfirefoxパッケージを導入しました。

$ sudo apt-get install firefox
uimとの相性が悪いらしい

これを起動してもエラーメッセージが表示されてしまいます。

$ /usr/bin/firefox
Error: in  unbound variable: uim-symbol-value-str

これを回避するためにGTK_IM_MODULE環境変数にscimを設定して起動するようにしています。

$ env GTK_IM_MODULE=scim /usr/bin/firefox

このまま使っても良かったのですが、daily-buildはビルドによって不安定になる可能性があるので少しだけ不安があります。

firefox-stableを導入

他に検索するとkarmic用に使えるリポジトリがあって、どうやらhardy用にもパッケージが準備されているようなので使ってみました。

apt-get用設定ファイルの追加

Ubuntu geekの記事でもそうですが、リポジトリの追加には/etc/apt/sources.listファイルを書き換えるようになっています。 しかし将来のアップグレードを考えると個別のパッケージ用の設定は/etc/apt/sources.list.d/ディレクトリ以下に個別にファイルを作成するのがお勧めです。

/etc/apt/sources.list.d/firefox-stable.listファイル

deb http://ppa.launchpad.net/mozillateam/firefox-stable/ubuntu hardy main
deb-src http://ppa.launchpad.net/mozillateam/firefox-stable/ubuntu hardy main
$ sudo apt-get update
firefox-3.6パッケージの導入

最初に書いたように前もってfirefoxパッケージを削除してから、firefox-3.6パッケージを導入します。

$ sudo apt-get install firefox-3.6

scimの使い心地

Ubuntu 8.04用のfirefoxは/usr/bin/firefox-3.0に3.0.17が残るので、 3.6を使うには/usr/bin/firefoxを起動するだけです。

SKKを使うためにuimを使っているのですが、freemindなどjavaと連携するところではscimよりも安定しています。uimが直接使えないのは残念ですが、いまのところscimで特に問題はないようです。

2010/02/07

Blogger APIでLabelを更新する

以前に投稿したBlogger APIでLabelのリストを取得するでラベルについて扱いました。 ブログ記事の投稿をBlogger API+anakiaで自動化した時の作業ログの一部だったのですが、最近になってブログ更新時にラベルのアップデートができていない事に気がつきました。

サンプルプログラムで未実装な機能

gdata-2.0.5に付属のサンプルプログラムは便利ですが、ラベル周りの取り扱いについては実装がありません。 だからというわけではないのですが、おそらくプログラミングした時に後回しにした機能をすっかり忘れていたのでしょう。不覚にも2ヶ月近く経ってから気がつく事になりました。

最近はIPv6周りの作業ばかりしていたので、気分転換を兼ねてさくっとラベルの過不足を判断するコードを追加する事にしました。

自作スクリプトの差分だけ

+    ## check the updated labels
+    terms = [] ## preserve all category items
+    remove_items = []  ## it will be removed later
+    for item in article.category:
+      terms.append(item.term)
+      if item.term not in labels:
+        ## It's safe to record the invalid item list.
+        ## If item is removed directly from article.category, then the loop will be hang up.
+        remove_items.append(item)  
+        pass
+      pass
+    ## remove old label
+    for item in remove_items:
+      article.category.remove(item)
+      pass
+    ## add new label
+    for item in labels:
+      if item not in terms:
+        article.add_label(item)
+        pass
+      pass

     ## commit the all changes
     self.client.update(article)
     pass

labelsリストには新しいラベル(type:str)が要素として入っている事がわかれば、他は解説は不要かと思います。 新たにメソッドに分割するべき分量ですが、とりあえず作業ログとして残しておきます。

atom要素を操作するためのwrapperとなるadd_labelメソッドが準備されているのは良いんですけどね、同じようなremove_labelメソッドぐらいは欲しかったかなぁ…。それは自作するから良いとして、やっぱりAPIはソースコードが公開されていないと触りたくないですね。

IPv6への移行は円滑に進むのか

ここはブログというよりも最近はメモ帳になっています。 今回はさっき投稿したip6tablesについてまとめている途中で書いていた文章をベースにしています。 少しぐちっぽいかもしれません。

IPv6への対応状況

実際にIPv6へ接続し、利用してみると、それなりに思うことがでてきます。

エンドユーザーの状況

端末からIPv6ネットワークへの接続自体はPHS以外にも各ISPが積極的にサービスを提供しています。 いってみればエンドユーザは、既にIPv6への接続準備が終りつつあるのが現状だと思います。

もちろんエンドユーザ側でのルータの新規購入とか、そういうコスト負担は必要です。 しかし、極端な話し、Internetを使いたいならそのコストを負担するべし、という事になり従うだけでしょう。 とはいえ、市販されているブロードバンドルータのIPv6対応があまり進んでいない事がかなり心配です。 まぁ「IPv6家庭用ルータガイドライン」も2009年6月にver. 1.0が出たぐらいですから、これから本格的に進むのでしょう。

サーバ側の状況

しかしサービスのIPv6対応はあまり進んでいない印象です。 IPv6普及・高度化推進協議会は「IPv4サーバ環境へのIPv6導入ガイドライン」を公開していますが、Web関連したサービスを提供する事業者に限定した文書になっています。

お名前.comのようなレジストリが提供しているDNSサービスで、IPv6のAAAAレコードを登録できると表明しているところを見つける事はできませんでした。 IIJなど登録できるDNSサーバがないわけじゃないですけれど、使えないところが圧倒的に多い印象です。 普段IPv4で使っているところが使えないことは、ごくごく普通に起こっていることです。

趣味でIPv6を使えるようにしてDNSがおまけで使えればおもしろいと思うんですよね。

エンドユーザからみれば、IPv4 or IPv6を意識する必要はないですが、水面下ではサーバ側(サービス)の対応が大きな問題になるように思えます。

サービスのIPv6移行

アドレスが枯渇するとはいっても、エンドユーザはCATVが導入している悪名高いキャリアグレードNATによりIPv4を使い続けることができます。

しかしサーバ側は固定IPv4アドレスを消費するため、将来的には市場から高価なIPv4アドレスを買い上げるという事が起こるでしょう。 その割り当ても効率的に行なえているわけではないですが、市場原理が働けばエンドユーザのIPv4アドレスは取り上げられISPによって企業向けに売られる事になるでしょう。

Webサーバの状況

サーバの中でもWebサーバは比較的対応が進んでいるのではないでしょうか。 その背景にはその多くが独立して稼働しているという事情も影響しているかもしれません。 Webサーバがクラスタ化されていても、実際にはフロントエンドのProxyサーバがIPv6に対応すればバックエンドはIPv4のプライベートアドレスのままで済むからです。

またWebサーバは組織の中になく、レンタルサーバを借りているだけという状況もあるでしょう。

SMTPサーバの状況

しかしSMTPサーバは通常は組織の内部に配置されています。 よく使うMLのMXレコードなど限られた範囲を調べた限りでは、ほとんどIPv6へは対応していないようです。 そもそもサーバ間の連携が必要なメールシステムでは一方だけがIPv6をしゃべれても意味がないでしょう。 それぞれIPv4とIPv6だけに対応しているSMTPサーバ間の通信をどうやって解決するのか興味深いところです。

また組織内部に配置されたSMTPサーバのトポロジを注意深く設計しないと、部分的にIPv6を導入したとたんにメールが届かないという状況も生まれそうです。

相手のいるサービスは頑張らないとIPv6に対応できない

まぁSMTPサーバだけがIPv4とIPv6アドレス両方を持たないといけないとか、そんな事は許されないでしょう。 かといってゲートウェイを経由するとなるとパフォーマンスやセキュリティの観点から、望ましい状況ではないですね。 ゲートウェイの前後をタッピングすればメールの内容がみえちゃいますから。 いまもそうですが、狙うところがより明確になるのが嫌です。

有名どころの大学をざっとみたところだと学術系でもサーバのIPv6対応は少ないようです。 IPv6対応がIPv6に特化したネットワークや基幹系ルータなどに留まっているように見えるのは少し先行きが心配です。

エンドユーザはISPが頑張ってIPv6アドレスを押し付けたり、OSがデュアルスタックを裏側で準備して何事もなくIPv6ネットワークに接続されるでしょう。 しかしサーバは、そう簡単に行きそうにありません。

サーバ/ネットワーク管理の状況

私自身はIPv4の経験はそれなりにありますが、IPv6については趣味で家庭のLANを接続するぐらいで、企業の基幹システムレベルのIPv6網を触った経験はありません。 ただそこにIPv6ネットワークが容易に構築されるとは到底思えません。

いきなりIPv6へ移行するわけではなく、部分的であっても最低数年間はIPv4とIPv6の両方のネットワークを管理する必要があるからです。

既存インフラへのIPv6ネットワークの追加

IPv4で稼働している対外的なサーバにIPv6を割り振る作業自体は簡単だと思います。

問題はバックエンドのシステムのIPv6対応をどうするかでしょう。 フロントエンドがIPv6に対応すればバックエンドはIPv4のままで良いという状況もありそうです。

オフィスエリアからの通信はIPv6になって、ゲートウェイを経由しないとIPv4なバックエンドサーバにアクセスできないというのは、冗談ですが、ある意味セキュアかもしれません。

普通はIPv4アドレスの割り振りの手続きが組織毎に決められているでしょうし、重複するような方法でさらに長いIPv6アドレスも管理するとなると、やってられないという心理的な抵抗はありそうです。

管理者のマインドは、しばらくIPv4アドレスでサーバを管理するという状況から抜けないように思います。 いきなりIPv6で全部を考えることはできないでしょうし、メリットもなさそうです。

IPv6に対応したエンドユーザが利用する範囲をIPv6に対応させていく、そういう状況が想像できます。 新規の環境は少なくともIPv4/IPv6のデュアルスタックになって、IPv4を捨てる日はそう近くならないでしょう。

インフラ管理者がIPv4、IPv6両対応のコストを払うのでしょう

しばらくは(実際は相当な長期間)IPv4アドレスでイントラネットが設計され続けるでしょうし、IPv6をバックエンドサーバの接続で使うことはないのかもしれません。

さきほど冗談で書いたIPv4の方が、むしろセキュアだとか屁理屈をつけてIPv4に留まろうと不毛な努力をするのかもしれません。 いずれにしても、そんなカオスな状況にあるインフラのIPv6移行へのコストは管理者が払うのでしょうね。

IPv4の資産を継承しないと決めた上位の決定は、結局のところ最終的に下位のインフラ管理者のところに、そのコストを転嫁しただけといえるのかもしれません。 技術的には理解できる決定ですし、正しいとは思います。 けれどコスト負担の構図は全員が正しく理解する必要があるでしょう。

現場が苦労して、それを利益を教授する人が意識もしないというのは、現代文明の性ですが寂しいところです。

さいごに

IPv6の導入のような大規模な変更こそ、既存の環境に加える形で、小さく導入して大きくしていく戦略が有効に思えます。 しかし、使わない or いつ使うかわからないIPv6のために、稼働確認の予算追加が認められるわけもないし、きっとどこかで困らないと進まないんでしょうね。

日本はIPv6の必要性を十分に認識していると思います。 しかし先を見通して先手を打てない状況は、(日本に限った事ではないですが)現代の日本が抱えているいろいろな問題の根本原因にもつながる体質のように思えます。

ネットワークが経済に与える影響を考えれば、サーバ側のIPv6対応はエンドユーザのIPv6対応以上に真剣に検討されなければいけないでしょう。

サービス(URL)にアクセス可能なサーバと不可なサーバが生まれれば、SOAなんていう単語が笑い飛ばされてしまうでしょうね。 実際、そういう状況に向いつつあると思います。 いきなりIPv6へ移行するのではなく、もっとゆるやかで長期的なプランを描いて、早く実行しておくべきだったんでしょうか。この事から何を学ぶべきなんでしょう。

2010/02/06

IPv6 - ip6tablesによるFirewall設定

IPv6用の設定にはip6tablesコマンドを使用します。 今回のスクリプトの中はだいたい、次の図のような流れになっています。

The ip6tables Process Flow

基本的な構成について

Feel6を使ったIPv6接続は、まだ手動で接続しています。 アドレス空間が膨大でDNSサーバに登録していないとはいってもポートスキャンの脅威がないわけではないですからね。接続の自動化よりもFirewallの設定を先にする事にしました。

設定したFirewallルールの確認

外出用にPHSカードは持っていて、IPv4のFirewallチェックにも使いました。 PRINはIPv6用のトンネルインタフェースも提供しているので、何も考えなくともPHSモデム経由でIPv6で接続する事ができるようになっています。

PRINのIPv6提供は実験的サービスのようですが、これが使えて助かりました。

PHSが使えると気がつくまでは、稼働の確認にSubnetOnline.comONLINE PING IPV6とONLINE PORT SCANNER IPV6も使いました。 IPv4向けにはいろいろなサービスがあるんですけどね、IPv6に対応した接続確認サービスを提供しているところは案外少ない印象です。

スクリプトの作成方針

まだ十分にIPv6の構造について勉強が進んでいるわけではないのですが、 とりあえず現行のIPv4用のiptables設定をベースにしています。

TCPヘッダのフラグチェックは同様に必要だよね、UDPも動きに変更ないよね、とか思いながら設定を削ったり付け加えたりしています。 ICMPはICMPv6になり機能は増え、考え方は変っていませんが、ARPの代替機能を持ったりしています。

ICMPv6は基本的に制限を加えない事にしました。 その代りログに出力をさせて将来必要なものと不要なものを振り分けるルールを追加しようとしています。 もし制限を加えるのであれば、後ろ向きですがecho-replyのように不要なものを個別に設定するのが良さそうです。

ログの取り方

使用頻度が低そうなところではログを取るようにしています。 必要に応じてACCEPT⇔logaccept、DROP⇔logdropを変更して使っています。

config_ip6tables.shスクリプト

次のようなスクリプトを/usr/local/sbinに配置して、interfacesファイルの中でpost-upに指定しています。

/etc/network/interfacesファイルの設定抜粋

auto dsl-provider
iface dsl-provider inet ppp
  pre-up /sbin/ifconfig eth0 up # line maintained by pppoeconf
  provider dsl-provider
  post-up /usr/local/sbin/config_iptables.sh
  post-up /usr/local/sbin/config_ip6tables.sh

ここで指定しているconfig_ip6tables.shの全体は次のとおりです。

config_ip6tables.shスクリプト

#!/bin/bash

## Initialize Script
PATH=/sbin:/usr/sbin:/bin:/usr/bin
export PATH

#######################
## Initialize Chains ##
#######################
ip6tables -F
ip6tables -X
ip6tables -Z

ip6tables -P INPUT DROP
ip6tables -P OUTPUT DROP
ip6tables -P FORWARD DROP

########################
## Creating new rules ##
########################
ip6tables -N insit  ## from internet to home-lan
ip6tables -N outsit ## from home-lan to internet
ip6tables -N logdrop        ## logging rule
ip6tables -N logaccept      ## logging rule
ip6tables -N dumpdrop       ## for un-expectation connections
ip6tables -N deny_ip_flags  ## for ip flag check
ip6tables -N deny_localaddr ## for local ip address group

## [logdrop] Prepare the logging interface
IP6TABLES_OPTS="-m limit --limit 5/minute -j LOG"
ip6tables -A logdrop ${IP6TABLES_OPTS} --log-level warn --log-prefix "netfilter6 drop "
ip6tables -A logdrop -j DROP

## [logaccept] Prepare the logging interface
ip6tables -A logaccept ${IP6TABLES_OPTS} --log-level debug --log-prefix "netfilter6 accept "
ip6tables -A logaccept -j ACCEPT

## [dumpdrop] Prepare the logging interface
ip6tables -A dumpdrop ${IP6TABLES_OPTS} --log-level warn --log-prefix "netfilter6 dumpdrop "
ip6tables -A dumpdrop -j DROP

## [deny_ip_flags] Prepare the logging interface
ip6tables -A deny_ip_flags ${IP6TABLES_OPTS} --log-level error --log-prefix "netfilter6 ipflagcheck "
ip6tables -A deny_ip_flags -j DROP

## [deny_localaddr] Prepare the logging interface
ip6tables -A deny_localaddr $IP6TABLES_OPTS} --log-level error --log-prefix "netfilter6 deny_localaddr "
ip6tables -A deny_localaddr -j DROP

##############################
## prepare the lo interface ##
##############################
ip6tables -A INPUT -i lo -j logaccept
ip6tables -A OUTPUT -o lo -j logaccept

##########################################
## prepare sit+ interfaces without sit0 ##
##########################################
## [insit] Default Chain Policy from outside attackers.
ip6tables -A INPUT   -i sit+ -j insit
ip6tables -A FORWARD -i sit+ -j insit
## [outsit] Default Chain Policy to stop leaking private connection.
ip6tables -A OUTPUT  -o sit+ -j outsit ## manage from localhost, so may be useless.
ip6tables -A FORWARD -o sit+ -j outsit ## manage from home-lan clients, so it's essential.

## [insit] check tcp flags before default rules
ip6tables -A insit -p tcp --tcp-flags ACK,FIN FIN -j deny_ip_flags
ip6tables -A insit -p tcp --tcp-flags ACK,PSH PSH -j deny_ip_flags
ip6tables -A insit -p tcp --tcp-flags ACK,URG URG -j deny_ip_flags
ip6tables -A insit -p tcp --tcp-flags FIN,RST FIN,RST -j deny_ip_flags
ip6tables -A insit -p tcp --tcp-flags SYN,FIN SYN,FIN -j deny_ip_flags
ip6tables -A insit -p tcp --tcp-flags SYN,RST SYN,RST -j deny_ip_flags
ip6tables -A insit -p tcp --tcp-flags ALL ALL -j deny_ip_flags
ip6tables -A insit -p tcp --tcp-flags ALL NONE -j deny_ip_flags
ip6tables -A insit -p tcp --tcp-flags ALL FIN,PSH,URG -j deny_ip_flags
ip6tables -A insit -p tcp --tcp-flags ALL SYN,FIN,PSH,URG -j deny_ip_flags
ip6tables -A insit -p tcp --tcp-flags ALL SYN,RST,ACK,FIN,URG -j deny_ip_flags

## [insit/outsit] Adding Deny Rules for local,
##   private reserved and broadcast addresses from outside.
ip6tables -A insit -s fe80::/8       -j deny_localaddr
ip6tables -A outsit -d fe80::/8     -j deny_localaddr

########################
## Default sit+ rules ##
########################
## - allow already used connections
ip6tables -A insit -m state --state ESTABLISHED,RELATED -p tcp -j ACCEPT
ip6tables -A insit -m state --state ESTABLISHED,RELATED -p udp -j ACCEPT
ip6tables -A insit -m state --state ESTABLISHED,RELATED -p icmpv6 -j ACCEPT
##
## - allow outbound connections
ip6tables -A outsit -m state --state ESTABLISHED,RELATED -p tcp -j logaccept
ip6tables -A outsit -m state --state ESTABLISHED,RELATED -p udp -j logaccept
ip6tables -A outsit -m state --state ESTABLISHED,RELATED -p icmpv6 -j logaccept
ip6tables -A outsit -m state --state NEW -p tcp -j ACCEPT
ip6tables -A outsit -m state --state NEW -p udp -j ACCEPT
ip6tables -A outsit -m state --state NEW -p icmpv6 -j ACCEPT

################################
## Define other special rules ##
################################
## for ICMPv6
ip6tables -A insit -p icmpv6 -j logaccept
ip6tables -A outsit -p icmpv6 -j logaccept
## for SSH connection
ip6tables -A insit -p tcp --dport 22 -j logaccept

##################################
## Adding Deny Rules as default ##
##################################
ip6tables -A insit -j dumpdrop 
ip6tables -A outsit -j dumpdrop

################################
## End of Processing for sit+ ##
################################


################################
## Default rules for eth[012] ##
################################
ip6tables -A INPUT -i eth+ -j ACCEPT
ip6tables -A FORWARD -i eth+ -j ACCEPT
ip6tables -A OUTPUT -o eth+ -j ACCEPT
ip6tables -A FORWARD -o eth+ -j ACCEPT

###################################
## Drop all packets with logging ##
###################################
ip6tables -A FORWARD -j dumpdrop
ip6tables -A INPUT -j dumpdrop
ip6tables -A OUTPUT -j dumpdrop

############
## Fin... ##
############
exit 0

2010/02/10追記:
輻輳を防ぐためにローカルに存在しないアドレスへの問い合せが外部に流れないように、フィルターでプリフィックス宛てのパケットをブロックします。
ついでに外部からプリフィックスを発信元とする不正なパケットの流入も禁止します。

fe80::/8の後ろに新たに追加した設定内容

...
ip6tables -A insit  -s 200x:xxx:xxx::/48 -j deny_localaddr
ip6tables -A outsit -d 200x:xxx:xxx::/48 -j deny_localaddr
...

さいごに

このスクリプトはとりあえずでっち上げたものなので、2、3日すると構造は変わると思います。 このまま使う事をお勧めするものではないので、ログをこまめに取るなどご注意ください。

2010/02/05

IPv6接続用DTCPクライアントをNet::POP3をベースに作ってみた

IPv6でのDTCP接続をする時に参考にさせて頂いたsetup-dtcp.plはNet::POP3の関数を呼び出すように設計されています。 自分で使うのに手を入れずらかったので、実験を兼ねてNet::POP3を継承したDTCPクライアントを作成してみました。

実際に使っているものは、もう少し手を加えていますが、最低限の機能を実現するバージョンを載せておきます。

Perlでの差分プログラミング

Net::POP3はpackageを使ったPerlクラスとして設計されています。 そこでAPOPのコードをベースにDTCP用のコードを追加してみました。

結果として、この範囲の機能だとかなり冗長な感じになってしまいました。 ただ自分で使う分には見通しが良くなったので、もう少し手を加えて使っています。

スクリプトの内容

 #!/usr/bin/perl

package YADtcpc;
use vars qw(@ISA $VERSION $debug);
use Net::POP3;
use Net::Cmd;
use Carp;
@ISA = qw(Net::POP3);

sub logit {
    my ($self, $msg) = @_;
    printf(STDERR "debug: %s\n", $msg);
}

## based on the 'sub apop'
sub _DTCP { shift->command('tunnel', @_)->response() == CMD_OK; }
sub dtcp {
    @_ >= 1 && @_ <= 3 or croak 'usage: $dtcp->dtcp( USER, PASS )';
    my($me,$user,$pass) = @_;
    my $banner;
    my $md;

    if (eval { local $SIG{__DIE__}; require Digest::MD5 }) {
	$md = Digest::MD5->new();
    } elsif (eval { local $SIG{__DIE__}; require MD5 }) {
	$md = MD5->new();
    } else {
	carp "You need to install Digest::MD5 or MD5 to use the DTCP command";
	return undef;
    }

    my $msg = ${*$me}{'net_pop3_banner'};
    return undef
	unless ( $banner = (${*$me}{'net_pop3_banner'} =~ /^([0-9A-z]+)\s+/)[0] );

    if (@_ <= 2) {
	($user, $pass) = $me->_lookup_credentials($user);
    }

    $md->add($user,$banner,$pass);

    $me->_DTCP($user, $md->hexdigest, 'network');

    $me->get_remote_link_addr();
}
sub get_remote_link_addr {
    my ($self) = @_;

    if ($self->message =~ /^(\d+[\.\d]+)\s+(\d+[\.\d]+)\s+([\d\:a-f]+)\/(\d+)/i) {
	return ($1, $2, $3, $4);
    } else {
	$self->logit("I couldn't get the correct response, then aborting now...");
	$self->quit();
	exit 1;
    }
}
sub _PING { shift->command('PING')->response() == CMD_OK }
sub ping {
    @_ == 1 or croak 'usage: $dtcp->ping()';
    my $me = shift;

    return () unless $me->_PING() && $me->message =~ /^([0-9A-z]+)/i;
    ($1 || 0);
}

##########
## main ##
##########
my $host = "dtcp.feel6.jp";
my $port = 20200;
my $user = "xxxxxx";
my $pass = "xxxxxx";

my $dtcpc = YADtcpc->new($host, Port=>$port);
my ($local,$remote,$prefix,$mask) = $dtcpc->dtcp($user, $pass);
printf("local=%s,remote=%s,prefix=%s,mask=%s\n", $local, $remote, $prefix, $mask);

while ($dtcpc->ping() =~ /pong/i) {
    printf("send ping message\n");
    sleep 60;
}

$dtcpc->logit("exit from while(1) loop because we ditn't receive the 'pong' message.");
$dtcpc->quit();

__END__
応用について

一応モジュールなので## main ##行より前半部分はYADtcp.pmとして使う事もできます。ただ、ここで分割する利点はないので一つの.plファイルとしてまとめています。

コード内部にパスワードを書くのは良いマナーとはいえないので、実際には別ファイルに分割していましょう。

バックグラウンドで動かすためには$ sudo -b ./yadtcpc.plみたいに動かす事ができますが、prefixなどの情報をファイルに書き出すようにしないとですね。 それに数回コネクションが切断してしまう経験もしたので、再接続のループは追加した方が良いでしょう。

だいたいこんな感じでしょうか。

2010/02/04

AlixのブロードバンドルータでIPv6

以前作成したalixなブロードバンドルータをフリービットのFeel6接続サービスを使ってIPv6に対応させてみました。

この手の作業は同じlinuxでもディストリビューションや環境に左右されるので、まず今回の環境をまとめておきます。

  • インターネット接続:NTT東日本 Bフレッツ + 固定IP
  • ブロードバンドルータHW:Alix 2C3
  • ブロードバンドルータSW:Debian lenny (eth0:Global ipv4 addr, eth1:192.168.1.1, eth2:192.168.10.1)
  • DTCPクライアント:自作Perlスクリプト
  • RAサーバ:debian lenny付属のradvdパッケージ from http://v6web.litech.org/radvd/dist/
  • クライアント1:Ubuntu 8.04 x86_64 (192.168.1.x)
  • クライアント2:Mac OS X 10.6.2 (192.168.1.x)

Feel6接続サービスへの登録

IPv6なネットワークに接続する方法はいくつかありますが、今回はフリービットのサービスを使う事にしました。 固定IPを使っているので静的にトンネルを作ってもらっても良かったのですが、将来プロバイダを変更するかもしれないですしね。

登録はFeel6のトップページから必要事項を送信するだけです。

DTCP接続を試してみる

Feel6のサーバにIPv6ネットワークへのトンネルを作ってもらうわけですが、その情報のやりとりはDTCPというプロトコルを使用するようです。 ここら辺の解説は「Linux 上で DTCP を使って IPv6 over IP トンネルを張る」がとても参考になりました。

検索してみるとDTCPの使い方としては、次のような方法がありました。

  • DTCPクライアントを自作する方法
  • USAGIプロジェクトが配布しているdtcpcコマンドを使用する方法

Linux 上で DTCP を使って IPv6 over IP トンネルを張る」にある"setup-dtcp"スクリプトやUSAGIプロジェクトのdtcpcを使ってみました。しかし内部で呼び出しているコマンドの実行に失敗するなど簡単にはできなかったため"setup-dtcp"の元になったスクリプトをベースに手作業でコマンドを実行していきました。

DTCPクライアントの自作

最終的には作り直したのですが、まずは参考にしたPerlスクリプトでトンネル状態を維持する事にしました。 参考にしたページにあるスクリプトでは変数定義が抜けているのと、Feel6から貰ったIPv6アドレスがわからないので出力ようにして、だいたい次のようなコードを追加しています。

#!/usr/bin/perl

my $host = "dtcp.feel6.jp";
my $port = 20200;
my $user = "xxxxxx";
my $pass = "xxxxxx";
...
    ($local, $remote, $prefix, $nbits) = ($1, $2, $3, $4);
    printf(STDERR "local=%s,remote=%s,prefix=%s,nbits=%s\n", $1, $2, $3, $4);
} else {
...

ここで$user$passには、Feel6登録時に入力したものを記入します。 実行に成功すると4つの文字列が手に入ります。

DTCPクライアントは接続を維持するために毎分PINGメッセージを送信しています。 そのためIPv6を使う間はずっと起動しておく必要があります。

AlixをIpv6ルータとして構成する

ここからはDTCPクライアントが裏側で動いている前提で書いています。

Feel6とは関係なしにBフレッツを使っているとNTTからIPv6アドレスが付与されます。 ただしこれは/64なプリフィックスなので、サブネット用に番号を加える事ができません。 また他の方のブログなどをみるとオプションを申し込まないと、NTTが準備したコンテンツ以外にアクセスする事は難しいようです。

Alixで確認すると、NTTから自動的に割り当てられるこのアドレスは以前Alixをルータにする投稿の中で、Bフレッツとの接続に使ったeth0に割り当てられています。

Bフレッツで自動的に割り当てられるIPv6アドレス

eth0      Link encap:イーサネット  ハードウェアアドレス 00:0d:xx:xx:xx:xx
          inet6アドレス: 2001:c90:489e:xxxx:xxxx:xxxx:xxxx:xxxx/64 範囲:グローバル
          inet6アドレス: fe80::xxxx:xxxx:xxxx:xxxx/64 範囲:リンク
...

Feel6から付与されるプリフィックスは/48ですから、前半64bitの残り16bit(0xffff分)を使ってネットワークを分割する事が可能になります。IPv4ではeth1, eth2でネットワークを分けているので、それにならってIPv6でもeth1, eth2でサブネットを設定する事にしました。

IPv6ルータになるための設定項目

Bフレッツ用のIPv6アドレスが割り当てられていた理由は、lennyのデフォルトとしてRouter Advertisement(RA)を受け付けるようになっている事に起因します。 ルータ自身がRAに反応しては困るらしくsysctlの設定を変更します。

Fee6を使う場合はRAを有効にしても大丈夫なんじゃないかという気はしつつ、先達に習うことにしました。 その他のアイデアとしてはeth0ではRAを受信してNTTのアドレスを構成しつつ、eth1, eth2はRAを無視して自分でアサインする、という構成も考えられましたが今回は試していません。

/etc/sysctl.confファイルの追加内容

...
net.ipv6.conf.all.accept_ra=0
net.ipv6.conf.all.accept_redirects=0
net.ipv6.conf.all.forwarding=1
...

また直接は関係ないですが、この他にsysctl.confではipv6関連の設定を2つ行なっています。

net.ipv6.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_source_route = 0
proc経由での設定変更について

sysctl.conf周りの設定は/procファイルシステムを通しても可能で、IPv6を説明した多くのサイトで$ echo 1 > /proc/sys/net/ipv6/conf/all/accept_raのようなコードが書かれています。 しかし恒久的な変更には/etc/sysctl.confファイルを使いましょう。

procに"0"や"1"を書き出したいが、sudoを使わないとという場合にはteeコマンドを使いましょう。

$ echo 1 | sudo tee /proc/sys/net/ipv6/conf/all/accept_ra

DTCPの出力からAlixにIPv6アドレスを付与する

DTCPクライアントの出力から次のような情報が得られています。

  • local - 自分のGlobal IPアドレス:219.117.xxx.xxx
  • remote - Feel6への接続用IPアドレス:43.244.xxx.xx
  • prefix - IPv6接続に使用するプリフィックス:2001:03xx:xxxx::/48
Alixの各インタフェースに割り当てる情報を決めていく

prefixは最後の2byte(16bit)が空いているので、ここを埋めてeth1, eth2にどのようなサブネットを設定するか考えます。 何でも良いのですが、IPv4に合わせて最後は0001,0010としました。

  • eth1 prefix - 2001:03xx:xxxx:1::/64
  • eth2 prefix - 2001:03xx:xxxx:10::/64

ここまでを踏まえてそれぞれのIPv6アドレスを決めました。 アドレスは何でも良いですが、"192.168.1.1", "192.168.10.1"を16進数に変換した"c0a8:0101", "c0a8:a101"を使う事にしました。

  • eth1 ipv6 addr - 2001:03xx:xxxx:1::c0a8:0101/64
  • eth2 ipv6 addr - 2001:03xx:xxxx:10::c0a8:0a01/64

ただしalixから外部に通信する必要がなければサイトローカルユニキャストアドレス(fe80::/64)で十分なはずで、必要だとしても2つも追加する必要はなかったりします。 まぁどちらかのデバイスをダウンさせる事もあるかもしれないので、こういう設定にしてみました。

実際にIPv6環境を構築していく

情報は決まったのでコマンドをパチパチ打っていきます。

トンネルの構築

remoteアドレスをベースにsit0, sit1インタフェースを構成します。

$ sudo /sbin/ifconfig sit0 up tunnel ::43.244.xxx.xx

Alixではiptablesを構成しているので、通常の構成ではカプセル化したパケットが出ていきません。 dropしたパケットのログをみると次のようになっています。

iptablesでdropしたパケットの情報

...
[135368.059979] netfilter drop IN= OUT=ppp0 SRC=219.117.xxx.xxx DST=43.244.xxx.xx LEN=124 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=41
...
[136225.974684] netfilter drop IN=ppp0 OUT= MAC= SRC=43.244.xxx.xx DST=219.117.xxx.xxx LEN=124 TOS=0x00 PREC=0x00 TTL=246 ID=16480 PROTO=41
...

このproto=41を通すためにiptablesに設定を追加します。

iptables -A inppp -s 43.244.xxx.xx --proto 41 -j ACCEPT
iptables -A outppp -d 43.244.xxx.xx --proto 41 -j ACCEPT
ルーティング情報の設定

eth0にはBフレッツから割り当てられたアドレスが付いています。 当然デフォルトゲートウェイはeth0に向いているので、このルーティングを削除しつつ、Feel6に変更する設定を行ないます。

$ sudo /sbin/route -A inet6 del ::/0 dev eth0
$ sudo /sbin/route -A inet6 add ::/0 dev sit1
eth1, eth2にIPv6アドレスを付与する

外部との通信に使用するために、IPv6でいうところのグローバルユニキャストアドレスを追加します。

$ sudo /sbin/ip -f inet6 addr add 2001:03xx:xxxx:1::c0a8:0101/64 dev eth1
$ sudo /sbin/ip -f inet6 addr add 2001:03xx:xxxx:10::c0a8:a101/64 dev eth2
ここまでの稼働確認

おそらくping6は既にインストールされていると思うので、これを使って接続が確立しているか確認します。

$ ping6 www.kame.net
$ ping6 2001:200:0:8002:203:47ff:fea5:3085

ping6 www.kame.netの出力

PING www.kame.net(orange.kame.net) 56 data bytes
64 bytes from orange.kame.net: icmp_seq=1 ttl=54 time=14.2 ms
64 bytes from orange.kame.net: icmp_seq=2 ttl=54 time=13.3 ms

ping6 2001:200:0:8002:203:47ff:fea5:3085の出力

PING 2001:200:0:8002:203:47ff:fea5:3085(2001:200:0:8002:203:47ff:fea5:3085) 56 data bytes
64 bytes from 2001:200:0:8002:203:47ff:fea5:3085: icmp_seq=1 ttl=54 time=14.6 ms
64 bytes from 2001:200:0:8002:203:47ff:fea5:3085: icmp_seq=2 ttl=54 time=13.5 ms

生のIPv6アドレスはwww.kame.netのものです。 将来的に変更されているかもしれないので、nslookupでアドレスを確認してからping6に渡してください。

$ nslookup -type=AAAA www.kame.net

nslookup -type=AAAA出力例

$ nslookup -type=AAAA www.kame.net
Server:		192.168.1.x
Address:	192.168.1.x#53

Non-authoritative answer:
www.kame.net	has AAAA address 2001:200:0:8002:203:47ff:fea5:3085
....

もしping6がインストールされていなければ、iputils-pingパッケージを導入してください。 nslookupはdnsutilsパッケージに含まれています。

$ sudo apt-get install iputils-ping

ここまでうまく行けば、radvdクライアントを動かすか、クライアント側を適切に設定すればIPv6ネットワークに接続する事ができるようになります。

トラブルシュート

もしping6の実行に失敗してデバイスを指定する次のようなコマンドが成功するとしたら、ルーティング情報が間違っているはずです。

$ ping6 -I sit1 2001:200:0:8002:203:47ff:fea5:3085

またping6実行時にping: sendmsg: Operation not permittedメッセージが出力されているとすれば、iptablesの設定に失敗しているか、ip6tablesが設定されているのが原因でしょう。

いまはIPv6用のfirewallは素通しの状態になっているはずで、次のコマンドでデフォルトが"(policy ACCEPT)"になっている事を確認します。

sudo /sbin/ip6tables -L

期待する出力

Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

radvdクライアントの起動

Alixではパッケージで配布されているradvdコマンドを使用します。

$ sudo apt-get install radvd

今回は次のような設定ファイルを準備しました。

/etc/radvd.confファイル

interface eth1
{
        AdvSendAdvert on;

        MinRtrAdvInterval 3;
        MaxRtrAdvInterval 10;

        prefix 2001:03xx:xxxx:1::/64
        {
                AdvOnLink on;
                AdvAutonomous on;
        };
};

interface eth2
{
        AdvSendAdvert on;

        MinRtrAdvInterval 3;
        MaxRtrAdvInterval 10;

        prefix 2001:03xx:xxxx:10::/64
        {
                AdvOnLink on;
                AdvAutonomous on;
        };
};

最後にradvdを起動して終りです。

$ sudo /etc/init.d/radvd start
radvdの初期設定について

パッケージに含まれる通常の構成だとAlixの再起動時に自動的にradvdが起動します。 これが悪いとはいいませんが、トンネルの構成をまだ自動化していないので意図しないタイミングでradvdが起動してしまうかもしれません。

今回はradvdを制御する目的で明示的に/etc/init.d/radvdスクリプトを実行する事にして、自動起動は停止する事にしました。

$ sudo mv /etc/rc2.d/S50radvd /etc/rc2.d/K50radvd

クライアントの接続確認

本当は手動でルーティングをしていたのですが、radvdを使う方法を書く事にしたのでクライアント側でする事はあまりありません。 Ubuntu 8.04 x86_64以外にMac 10.6でもブラウザをいくつか立ち上げて確認をしています。

Ubuntu 8.04での稼働確認

accept_raは有効になっているので、基本的にIPアドレスはすでに割り当てられていました。 しかしufwを有効にしているせいかip6tablesのデフォルトポリシーがDROPになっていたためping6の実行に失敗しました。

ip6tablesの構成は後でする事にして、いまは全部の通信を許可しています。

$ sudo /sbin/ip6tables -P INPUT ACCEPT
$ sudo /sbin/ip6tables -P OUTPUT ACCEPT
$ sudo /sbin/ip6tables -P FORWARD ACCEPT

とりあえずWebブラウザだけを使って、keme.netの亀が踊るかどうか確認します。

http://www.kame.net/

Mozilla Firefox 3.0.17, Google Chromium 5.0.315.0 (37939) Ubuntu, Opera 10.10で確認しました。 結果としてOperaだけがIPv4で接続していました。

OperaはIPv6周りののバグ対策として、とりあえずIPv6を使えなくしたようですね。 一時的とはいえ後ろ向きな感じがしなくてはない対応ですが、フレームワークの問題で抜本的な対策が難しかったのかなぁ…。

Mac 10.6での稼働確認

Ubuntu同様に自動的にグローバルアドレスを受信しているので、Safariなどで何も考えなくても亀が踊っています。Opera 10.10を試してみたところやはりIPv6では接続できませんでした。

さいごに

Bフレッツのアドレスを使えるのであれば、eth0とbound0を組んでradvdだけ起動してしまえば使えそうな気もします。 それにサブネットを分ける必要性があるのか、という気もします。 まぁせっかくalixにはNICが3つも付いているので、これもまた良いでしょう。

しかし、一応動いているとはいえ頭の中が完全にIPv4のネットワークのマナーに染まっているので、IPv6的な考え方がまったくできていないと実感しています。 ここに書いた対応も成功事例ではありますが、本当のベストプラクティスとはいえないところがあるだろうと思います。まぁしばらくIPv6で遊んでみるつもりです。

この後の残作業はFirewall周りの他に、基本的なところでDTCPクライアントからeth1, eth2にサブネットワークを構成して、そのアドレスを反映したradvd.confを作成するところまでを自動化する必要があります。

まっさらなところからの自動化自体は簡単ですが、途中からのやり直しなんかを考えると、各作業の粒度を考えて個別のスクリプトにしないと安定しそうにありません。これも少し時間を考えて試行錯誤したいと思います。

とりあえずは次のステップはfirewallの設定ですかね。