2011/05/20

Amazonアソシエイトの広告が表示されない問題の原因

このブログはAmazonアソシエイトというアフィリエイトへ参加しています。恐らくこの記事の右側にもAmazonの広告が表示されているでしょう。また一部の記事では、紹介した商品の広告を記事の下へ表示させています。

しかし自分のマシンからこのブログへアクセスすると、これらAmazon広告の一部が正しく表示されない、という現象が度々発生していました。具体的には、広告の一部が表示されなかったり、広告領域全てが表示されなかったり、といった具合です。


毎回起こるわけではありませんが、連続してリロードした場合などには、大変高い頻度で発生します。アクセスに使用するマシンはiMac、MacBook Airどちらでも生じます。また、Chrome、Safari、Firefox、何れのブラウザを用いても発生します(頻度は異なります)。Amazonの広告タイプやサイズを色々と変更してみたりもしたのですが、変化はありません。

Amazon側の問題だろう、と気楽に考えていたのですが、ふとWindows PCからアクセスしてみたところ、何度リロードを行っても広告が欠けないことに気が付きました。問題は私のMacOS X環境だったようです。

Amazonの広告はiframe内に表示されており、広告コンテンツはsrcアトリビュートで指定されたURLから取得されています。例えば「Amazonライブリンク」という広告用ウィジェットを用いた場合、srcへは次のようなURLが指定されています。

http://rcm-jp.amazon.co.jp/e/cm?t=foo&o=9&p=14&l=bn1&mode=books-jp&browse=202188011&fc1=000000&lt1=_blank&lc1=856953&bg1=FFFFFF&f=ifr;

表示が不正になった状態でiframeのソースを確認したところ、HTMLが途中で切れてしまっていることに気が付きました。

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=shift-jis" /> <style type="text/css"> /* Standard CSS */ body, div, p {   margin:0px;   padding:0px; } img  {   border:none;  }  a {   color:#856953; } div#amazoncontent {   font-family:'Arial', sans-serif;   font-size:10px;   color:#000000;   background-color:#FFFFFF;   position:relative;   border:1px solid #000000;   overflow:hidden;   text-align:left; } div#logo {   text-align:center;   background-color:#000000; } div#privacy {   position:absolute;   bottom:0px;   width:100%;   text-align:center;   padding:3px 0px;   color:#a1a1a1;   background-color:#FFFFFF;   z-index:1;   clear:both; } div#privacy a {   color:#a1a1a1; }  /* Custom CSS */  div#amazoncontent {   width:158px;   height:598px; } div.product {   position:relative;   padding:3px;   height:76px;   margin-left:65px;   background-color:#FFFFFF; } img.productImage {   position:absolute;   left:-65px;   top:3px; } div.prices {   color:#990000;   margin:1px 0px; } p.author {

この為、ブラウザがうまく広告を表示出来ないようです。

ブラウザ以外のアプリケーションでによる事象を確認するため、MacOS Xへ同梱されているcurl(1)を使い、iframeへ指定されていたURLへアクセスしてみました。

% curl -o foo 'http://rcm-jp.amazon.co.jp/e/cm?t=foo&o=9&p=14&l=bn1&mode=books-jp&browse=202188011&fc1=000000&lt1=_blank&lc1=856953&bg1=FFFFFF&f=ifr;'

するとcurlを何度も連続で実行させた場合に、同じくHTMLが途中で切れてしまう事象が確認できました。この事象は、アプリケーション特有のバグではなく、より深いレイヤで発生しているようです。

次にネットワーク上の伝送が正しいかどうかを検証するため、tcpdumpにより、ネットワークパケットを調べてみました。curl実行時、HTTPデータを圧縮することなく平文で伝送させるように指定し、全てのデータを一旦記録させ、その後、記録したデータを-Aで眺めてみることにします。

なお、iframeのsrcで指定されているホスト「rcm-jp.amazon.co.jp」はDNSラウンドロビンが設定されている為、tcpdumpでのhost指定はIPアドレスで指定すると良いでしょう。

% sudo tcpdump -w dump -s 0
% curl -o foo 'http://...'
% tcpdump -r dump -A -n host 207.171.187.112

記録したパケットを眺めてみたところ、curlではファイルが切れている状況でも、ネットワーク上は全てのデータが適切に到達していることが分かりました(但し以前の記事で指摘したように、Content-Lengthの値は不正です)。どうやら、デバイスドライバからアプリケーションへ至る間で、パケットが消失してしまっているようです。

問題発生箇所を調べるために、curlコマンドを、今度はdtrussコマンドと共に実行させてみます。dtrussは、コマンドのシステムコール呼び出し状況を表示してくれるコマンドで、Leopard以前に含まれていたtrussをDTraceを用いて実装したものです。

% sudo dtruss curl -s 'http://...'

すると次のような行が目につきました。

select(0x4, 0x7FFF5FBFEBE0, 0x7FFF5FBFEB60, 0x7FFF5FBFEAE0, 0x7FFF5FBFEC80)   = 1 0
recvfrom(0x3, 0x10082DA00, 0x4000)   = 1460 0
<省略>
select(0x4, 0x7FFF5FBFEBE0, 0x7FFF5FBFEB60, 0x7FFF5FBFEAE0, 0x7FFF5FBFEC80)   = 1 0
recvfrom(0x3, 0x10082DA00, 0x1004)   = 0 0

この流れを見る限り、単一プロセスで作られた一般的なネットワーククライアントプログラム同様、curlでは、select(2)でネットワークからのデータ到達を待ち受け、ディスクリプタの準備ができた事を確認した後、recvfrom(2)で実データを読み込んでいると考えられます。

しかし1回目のrecvfromで1460バイトのデータを読み込んだ後、2回目の準備完了を受けて読み込んだデータサイズは0バイト、即ちデータの終端となっています。このHTMLファイルは概ね5000バイト強になる筈ですから、これはおかしな事象です。

curlのソースコードを確認してみましたが、実装上不審な点は見当たりません。またtcpdumpが記録したTCPシーケンスも確認しましたが、RSTで切断されているといった問題もなさそうです。

ここまでで「select上はディスクリプタからの読み出し準備が整った状況でありながらも、実際にはデータを読み出すことが出来ない状況にあるのでは」と推測しました。この仮説が正しければ、ブラウザがHTMLを途中までしか読めていない現象も理解できます。

この仮説を検証するため、簡単な再現プログラムを作ってみました。このプログラムは、以下のURLから入手可能です(本プログラムの利用は自由です。著作権等は主張しません。無保証です。エラーチェックなども行っていません)。


機能は単純で、指定ホストの指定ポートに対して指定されたコマンドをHTTP/1.1で送信し、応答を受信します。但しselectで読み込み完了になった後、データ読み込みに先立ち、指定されたマイクロ秒だけ待たせることが出来ます。また、何回取得を繰り返すかも指定できます。

まずは待ち時間を0秒、50回繰り返しでコマンドを実行させ、recvfromが読み込んだバイト数を確認してみました。並行してtcpdumpも実行させます。

% ./c rcm-jp.amazon.co.jp 80 0 'GET /e/cm...' 50

結果selectが読み出し可能状態にあり、またネットワーク上も残存データが存在しているにも関わらず、recvfromが0を返す状況を再現させることができました。

ここで、selectが読み出し可能状態になってから後、100ms経過後にrecvfromを実行するようにしてみました。

% ./c rcm-jp.amazon.co.jp 80 100 'GET /e/cm...' 50

先ほど発生していた「残存データがあるにも関わらずrecvfromがデータを読めない」という状況は全く発生しません。

ここまで来てようやく問題の原因に気が付きました。この事象を引き起こしていたのはAntiVirusプログラムでした。

私はMacOS X上では、AntiVirusプログラムとしてVirus Barrier X6を使っています。このプログラムにはWeb Threatsという名の機能が備わっており、マニュアルによれば次のとおりです。

VirusBarrier X6 offers protection from web threats, such as cross-site scripting attacks, drive-by downloads, clickjacking, as well as web pages with malicious JavaScript or malicious Java applets.

この機能を有効にすると、Amazonの広告HTMLへ含まれているJavaScriptが検査されることになり、その検査時間分、読み可能状態になるまで遅延が発生するのだと思われます。

本来はVirusBarrierによる安全性の検査が終了してから後、selectに対して準備完了を返すようにすべきだと思うのですが、残念ながらそのような実装にはなっていないのでしょう。結果、VirusBarrierの検査終了前にrecvfromが実行されてしまうと、未だ読み込み可能データが無いことから0バイトが返されてしまい、データ終端だと認識してしまうということなのだと思います。

結局、このWeb Threat機能をオフにして試してみたところ、ブログ上のAmazon広告が正しく表示されるようになりました。

その後、幾つかのJavaScriptファイルを使っているサイトで検証を行ってみましたが、Amazonの広告以外では、この現象が確認できるサイトを見つけることは出来ませんでした。Amazonのファイルは、解析に必要なスクリプトの複雑さなどが絶妙なのかも知れません。

もし、Virus Barrierをお使いの環境で、Webサイトの一部が欠ける、などの事象が生じた場合には、Virus BarrierのWeb Threat機能が原因ではないかを確認してみると良いでしょう。

2011/05/08

続・NEC AtermWR8700N を購入し実効速度を計測しました

「NEC AtermWR8700N を購入し実効速度を計測しました」で「AtermWR8700N」と「AtermWL300NE-AG」との接続速度の計測を行い、有益な結果を確認できました。


「AtermWR8700N」システムの導入により、BUFFALO製とNEC製、2組の802.11n対応アクセスポイントならびにイーサネットコンバータが揃いましたので、これら相互接続性を検証してみました。なお、BUFFALO、NECとも、他社機器との接続性は保証していませんのでご注意ください。

まずはアクセスポイントにNECのAtermWR8700Nを利用し、イーサネットコンバータにBUFFALOの「WLI-TX4-AG300N」を用いて速度計測を行います。

設定は40MHzチャンネルボンディング+2ストリーム構成、周波数は2.4GHz帯を使います。その他の環境は「NEC AtermWR8700N を購入し実効速度を計測しました」と同様です。

  1. 66.31Mbps(8487.97KBps)
  2. 66.86Mbps(8558.44KBps)
  3. 67.04Mbps(8581.31KBps)
  4. 64.21Mbps(8219.02KBps)
  5. 66.60Mbps(8525.84KBps)

平均は66.59Mbps。異なるメーカ機器間での接続でありながら、純正環境で2.4GHz帯域を使用した場合と同様の伝送速度を得ることが出来ました。

しかし残念ながら、この組み合わせで暫く使用していると、パケットロスが発生し始めてしまいます。最短では1分ほどで発生することもあり、10分ほどのping計測結果では、平均50%のパケットが失われてしまいました。実質、この組み合わせで利用することは不可能なようです。勿論、WHR-HP-G300NとWLI-TX4-AG300NによるBUFFALO同士の組み合わせでは問題なく動作しますから、機器の不具合ではなく、相性または設定の問題なのでしょう。

次に、先ほどと同じくAtermWR8700NとWLI-TX4-AG300Nを用い、周波数帯域のみを5GHz帯へ変更して速度検証を行いました。

  1. 85.33Mbps(10923.36KBps)
  2. 85.67Mbps(10966.81KBps)
  3. 85.52Mbps(10947.39KBps)
  4. 85.30Mbps(10919.21KBps)
  5. 85.37Mbps(10928.09KBps)

平均速度は85.39Mbps。前回の調査ではNEC同士の機器を組み合わせた平均速度が78.83Mbpsでしたので、驚くことに、同一メーカでの組み合わせよりも約7Mbps程速度が向上したことになります。念のため、同一時間帯にNEC同士の組み合わせで再度計測し直しましたが、やはりNEC同士の組み合わせでは80Mbpsを超えることはありませんでした。

勿論、今回の伝送速度計測は厳密なものでは無いため、環境などに伴う誤差は十分に考えられますが、興味深い結果です。また2.4GHz帯での場合と異なり、10分ほど計測してもパケット消失は一切生じませんでした。

最後に、アクセスポイントへBUFFALOのWHR-HP-G300Nを、イーサネットコンバータにNECのAtermWL300NE-AGを組み合わせて計測を試みました。WHR-HP-G300Nの仕様から2.4GHz帯のみでの検証です。

しかし、残寝ながら、この環境では接続を確立することが出来ませんでした。AtermWR8700NとWLI-TX4-AG300Nとの不安定さも併せて考えると、2.4GHz帯での異機種接続は、やや難があるようです。2.4GHz帯は、各社共に伝送速度や安定性向上を実現するため、細かなチューニングを施していますので、その差を適切に吸収し、標準に準拠するよう設定し直す必要があるのでしょう。

参考値として、同じ場所へMacBook Airを設置し、5GHz帯を用いた速度を計測してみました。

  1. 100.66Mbps(12884.78KBps)
  2. 99.97Mbps(12796.20KBps)
  3. 99.642Mbps(12754.24KBps)
  4. 102.08Mbps(13065.94KBps)
  5. 100.50Mbps(12865.00KBps)

イーサネットコンバータでは無線/有線変換処理が行われていること、そもそもイーサネットコンバータ計測時の環境がMacBook AirとUSBで接続された有線LANアダプタコネクタ経由であり、ここでもオーバーヘッドが見込まれることなどを勘案すれば、量イーサネットコンバータ共に、十分優秀な速度であると言えそうです。

今回の結果を見る限り、既にWLI-TX4-AG300Nを用いている環境で、5GHz帯域での802.11nの利用を検討している場合には、イーサネットコンバータの付属していないAtermWR8700N単体パッケージを購入し、試用してみ、というアプローチもあるのかもしれません。

なお、上記は接続可能性を保証するものではありませんので、ご利用に当たっては、その点を十分にご留意頂ければと思います。

2011/05/06

NEC AtermWR8700N を購入し実効速度を計測しました

最近はホームネットワークといえば無線LANが一般的になり、自前のアクセスポイントを稼働させる家庭が増えてきました。これに伴いアクセスポイントのチャンネル重複も一般化、住宅地にある我が家でも、全てのチャンネル上で複数個のアクセスポイントを確認できます。

このチャンネル重複の為か、最近無線LANの動作が不安定になることが増えてしまいました。特に木造二階建てである我が家では、1F/2F間のバックボーンを無線LANで構築しているため、この影響はホームネットワーク全体へと及んでしまいます。

但しチャンネル重複が見られるのは2.4GHz帯のみ。5GHz帯にはアクセスポイントが一切存在しません。そこで5GHz帯での802.11nをサポートしたAtermWR8700Nを試してみることにしました。実際に購入したのはイーサネットコンバータ「AtermWL300NE-AG」とのセットである「PA-WR8700N-HP/NE」です。


AtermWR8700Nは昨年春の発売以来、人気の続く製品で、相当の期間品切れが続いていました。実際に利用した方の評判も高く、いつかは試してみたいと思っていた製品です。

現在使用している無線LANシステムは「WHR-HP-G300N+WLE-2DAの実効速度検証(続WZR-HP-G300NHの実効速度検証)」でまとめたように、BUFFALOのアクセスポイント「WHR-HP-G300N」へ、オプションアンテナ「WLE-2DA」を接続した構成。


「WHR-HP-G300N」は40MHzチャンネルボンディング+2ストリーム対応をサポートしており、理論値300Mbpsでの通信が可能な802.11nアクセスポイントです。但し対応周波数帯域は2.4GHz帯のみであり5GHz帯はサポートしていません。

このアクセスポイントを2Fへ、1Fには「WLI-TX4-AG300N」を設置し、1F/2F間のバックボーンを構成しています。「WLI-TX4-AG300N」も40MHzチャンネルボンディング+2ストリーム対応の製品であり、加えて2.4GHz/5GHzの両帯域へ対応している点が特徴です。但し「WHR-HP-G300N」との組み合わせでは、2.4GHz帯でしか利用できません。

以前計測した際の伝送速度は平均60.38Mbpsでしたが、今回、AtermWR8700Nの導入に伴い、改めて現在の環境でも速度を計測をしてみることにしました。

伝送データ量は前回同様約80Mバイト、認証や暗号化も前回と変化ありません。但しftpを使った前回とは異なり、今回はttcpで計測してみます。また「WLI-TX4-AG300N」側に接続するのはIdeaPad S9eではなくMacBook Airです。

結果は以下の通りでした。

  1. 63.39Mbps(8114.04KBps)
  2. 62.71Mbps(8026.72KBps)
  3. 63.32Mbps(8105.09KBps)
  4. 63.00Mbps(8063.62KBps)
  5. 63.63Mbps(8144.85KBps)

最高・最低速度を除く平均は63.24Mbps。前回の計測とほぼ変化ありません。

次にこれらのシステムを「AtermWR8700N」+「AtermWL300NE-AG」へ置き換え計測してみました。両製品とも40MHzチャンネルボンディング+2ストリーム対応であり、且つ2.4GHz/5GHz帯域の何れでも接続が可能です。

まずは2.4GHz帯での速度を計測してみました。

  1. 62.41Mbps(7988.54KBps)
  2. 61.39Mbps(7858.44KBps)
  3. 61.93Mbps(7927.46KBps)
  4. 66.47Mbps(8507.73KBps)
  5. 63.32Mbps(8104.79KBps)

最高・最低速度を除く平均は62.56Mbps。概ねWHR-HP-G300N構成と変化ありません。

今度は、アクセスポイント、イーサネットコンバータを5GHz帯域で接続してみます。

  1. 78.30Mbps(10023.02KBps)
  2. 78.68Mbps(10070.91KBps)
  3. 78.39Mbps(10034.01KBps)
  4. 79.93Mbps(10231.26KBps)
  5. 79.43Mbps(10166.95KBps)

最高・最低速度を除く平均は78.83Mbps。同じ機材を用いながら、使用帯域を5GHzへと変更することで、先程より約16Mbpsも速度が向上しました。

5GHz帯は2.4GHz帯に比べ障害物耐性が弱いため、我が家の環境で5GHz帯を併用した場合、「動作の安定性は増すが、転送速度は低下する」との結果を予想していました。しかし実際には、速度が向上するという嬉しい誤算に。近隣で誰も5GHz帯を使っていないため、MIMOが理想的な環境で動作し、速度向上が実現されたものと思われます。

AtermWR8700N導入にあたり、気になっていたもう一点の懸念は、外部アンテナの差でした。WHR-HP-G300N環境では、指向性の強い外部アンテナを併用することで安定性を強化していましたが、今回は特別なアンテナを付与していません。この点で伝送速度や安定性に問題が生じるのでは、との懸念を抱いていました。しかし実効速度を見る限り、この懸念も杞憂に終わりました。AtermWR8700Nは「ハイパーロングレンジ」の名称のもと電波の到達性を売りにしており、こうした環境での利用も想定しているのでしょう。

さて5GHzと言えば「電子レンジと干渉しない」ことも利点の一つに挙げられます。我が家では電子レンジと無線LANアクセスポイントとは、なるべく離して設置していますが、それでも電子レンジを稼動させると、ネットワークの伝送速度が著しく低下してしまいます。5GHの導入に伴い、その点も検証してみました。

AtermWR8700N環境を2.4GHz帯域で動作を行なった場合、pingの応答は約2-7msの範囲で安定しています。この状態で電子レンジを稼動させると、途端に応答が最長43msまでの遅延され、また応答間隔も非常に不均一になってしまいました。従来の環境で電子レンジを利用した場合と同様の現象です。

この構成を5GHz帯域で稼動させた場合、pingの応答は約2-5msでした。この状態で電子レンジを稼動させてみましたが、応答速度に変化は見られず、継続して安定した伝送が行えました。

この点からも、5GHz帯システムの導入により、ホームネットワークをより強固にすることができたと言えます。

結果AtermWR8700Nを導入することにより、到達性を維持したまま、伝送速度と安定性を向上させることが出来ました。5GHz帯へ移行したことにより、利用可能なチャンネル数も拡大、将来的にも他アクセスポイントと重なる可能性を減らすことが出来ました。

またAtermWR8700Nは、他社同等製品と比べ、設定のわかりやすさや、マニュアル記載事項の細かさ、有線ポートがGbEである点など、製品それ自身にも大きな利点があります。こうした安心感も満足度を高めてくれました。

総合的に、不満と感じる点のない大変満足度の高い製品であり、これから無線LAN環境を導入しようと考えている全ての方へお薦めしたい製品だと感じています。

2011/05/03

facebook の「いいね!」ボタンを Blogger の各記事へ対応させる方法

facebookの「いいね!」ボタンは、faebook DEVELOPERSページから、簡単にコードを取得することが出来ます。勿論、このコードをBloggerで利用することも出来ます。

但し「URL to Like」へブログのトップページURLを指定した場合、ブログ全体で「いいね!」が共有されてしまいます。

各記事に対して「いいね!」を指定させたい場合には、生成されたコードのsrcアトリビュートをexpr:srcへと変更し、その上で、data:post.urlを反映させるように設定します。

例えば、このブログのトップページを指定した「いいね!」ボタンのコードは次のようになります。

<iframe src="http://www.facebook.com/plugins/like.php?href=http%3A%2F%2Fblogger.tempus.org%2F&amp;send=false&amp;layout=button_count&amp;width=450&amp;show_faces=false&amp;action=like&amp;colorscheme=light&amp;font&amp;height=21" scrolling="no" frameborder="0" style="border:none; overflow:hidden; width:450px; height:21px;" allowTransparency="true"></iframe>

ここで、srcをexpr:srcへ変更し、「http%3A%2F%2Fblogger.tempus.org%2F」と記されている箇所をdata:port.urlへと変更します。data:post.urlの前後に位置する元のsrcアトリビュート部は、&quot;で囲みます。それら3つのパートは「+」で結合しましょう。最後にexpr:src全体をシングルクォートで囲みます。

最終的なコードは以下の様になります。

<iframe expr:src='&quot;http://www.facebook.com/plugins/like.php?href=&quot; + data:post.url + &quot;&amp;send=false&amp;layout=button_count&amp;width=450&amp;show_faces=false&amp;action=like&amp;colorscheme=light&amp;font&amp;height=21&quot;' scrolling="no" frameborder="0" style="border:none; overflow:hidden; width:450px; height:21px;" allowTransparency="true"></iframe>

このコードへは、このブログに依存している情報は含まれていませんので、Bloggerを利用しているどのブログでも利用可能です。

tipsでした。

2011/05/02

sshでHostbasedAuthenticationを有効にする場合の落とし穴

以前、「ssh で HostBased 認証を行うには ssh-keysign に setuid する必要がある」として、HostBasedAuthentication(以下、ホストベース認証)の設定方法をまとめました。

にも関わらず、ホストベース認証を有効化しようとすると、相変わらず落とし穴にはまってしまうことが少なくありません。以下、自分が陥った落とし穴をまとめてみました。

ホストベース認証を有効にする場合、loginされる側(以下サーバ側)では、原則以下の3手順が必要となります。

  1. sshd_configでHostbasedAuthenticationをyesに設定する
  2. ssh_known_hostsへ、loginする側(以下クライアント側)の公開鍵を登録する(クライアント側でもsshdが起動している場合にはssh-keyscanコマンドの利用が便利)
  3. shosts.equiv、または ~root/.shosts へクライアントのホスト名を登録する

一方クライアント側では以下の2手順が必要になります。

  1. ssh_configでEnableSSHKeysignをyesに設定する
  2. ssh_config、または~/.ssh/configでHostbasedAuthenticationをyesに設定する

以下、陥りがちな落とし穴について解説します。


1. 設定ファイルの置かれているディレクトリ

サーバ側、クライアント側両者に関係する落とし穴です。

サーバ側で起動するsshdコマンドやクライアント側で利用するsshコマンドは、
システムワイドの設定ファイルを参照しますが、
このファイルが置かれるディレクトリはOSやコンパイル時の設定等に依って異なります。

例えばFreeBSD 8.2やUbuntu 11.04へ同梱されているsshパッケージでは、システムワイドな設定ファイルは/etc/sshへ置かれています。一方、MacOS X 10.6に含まれているsshパッケージでは、/etcへ置かれています。

また、FreeBSDやMacOS Xを利用している場合でも、portsなどのパッケージ管理ツールを使ってインストール行った場合には、それぞれ設定ファイルの置かれるディレクトリが異なります。

例えば「依存関係解決の為、パッケージ管理ツールが最新版のsshをインストールしていた」などの理由により各種コマンドが更新され、その結果参照する設定ファイルが異なっていた、ということは皆無でありません。特に、起動時に絶対パス指定が必須なsshdコマンドと異なり、相対指定が可能なsshコマンドの場合には見落としがちです。

設定ファイルを変更しても、変更が反映されていないと感じる場合には、どの設定ファイルが参照されているのか確認してみると良いでしょう。ssh の場合には -v を、sshd の場合には -d を指定することで、参照する設定ファイルが表示されます。以下は、sshが参照している設定ファイルに関する表示例です。

debug1: Reading configuration data /Users/n-miyo/.ssh/config
debug1: Reading configuration data /etc/ssh_config


2. 設定の順序

sshの設定は、コマンドライン指定値、ユーザ独自の設定値(一般的には~/.ssh/configでの設定値)、最後にシステムワイドなssh_configの順番で探索され、最初に見つかった設定値が採用されます。各種設定ファイル内に於いても、ファイル先頭行から探索が行われ、最初に見つかった設定値が採用されます。即ち、先勝ちモデルです。

sshdでの設定も同様で、sshd_configの中で、ファイル先頭業から先勝ち優先モデルに基づき探索が行われます。

独自設定を行う場合には、設定ファイル内の上部で同じ設定がなされていないことを確認する、または、ファイル先頭付近で設定を行うと良いでしょう。特に ssh_config、または ~/.ssh/config でホスト名を指定した設定を行う場合には、その設定が「Host *」指定よりも前で行われていることを確認しましょう。「Host *」よりもファイル終端側で設定を行っても、その設定は反映されません。


3. shosts.equivで指定するホスト名

サーバ側の設定に関係する落とし穴です。

ホストベース認証では、root以外のユーザによるログインを許可する場合、接続を許可するクライアントのホスト名を、サーバ側のシステムワイドな設定ファイルであるshosts.equivへ列記する必要があります。

shosts.equivへ書きこむホスト名は、sshd_configで設定したUseDNSの値によって異なります。

sshd_configでUseDNSをyesに設定してている場合、ホスト名には、IPアドレスの逆引き結果である正式ホスト名、またはIPアドレスを指定します。もしサーバがホスト名解決に/etc/hostsを利用している場合、そこへ記された正式ホスト名を書く必要があります。一方サーバがNISやDNSで名前解決を行っている場合、それらのシステムから返されるホスト名を書かなければなりません。何れの場合でも、正式ホスト名の代わりに、別名(alias名)を書いた場合、認証に失敗します。

例えば、名前解決に/etc/hostsを利用しているサーバに於いて、/etc/hostsへ以下の記述があったとします。

10.0.0.1 foo.example.jp foo

この場合、shosts.equivで指定するホスト名はfoo.example.jpです。fooを指定しても認証は成功しません。

一方、sshd_configでUseDNSがnoの場合、ホスト名には必ずIPアドレスを書く必要があります。ホスト名や別名、即ち上記の例では、foo.example.jpやfooを書いても認識されません。

クライアントが送ってきたホスト名と、sshdが認識したホスト名が異なった場合、UseDNSがyesに設定されているsshdは次のようなエラーメッセージを出力します。

May  2 07:56:38 lidia sshd[29264]: userauth_hostbased mismatch: client sends foo, but we resolve 10.0.0.1 to foo.example.jp

一方、sshd_configでUseDNSがnoになっている場合、次のようなエラーメッセージが出力されます。

May  2 07:58:06 lidia sshd[54965]: userauth_hostbased mismatch: client sends foo.example.jp, but we resolve 10.0.0.1 to 10.0.0.1

システムがどのように名前解決を行っているかを知る際の参考になるでしょう。


4. ホストベース認証利用時によるrootのloginの許可

サーバ側の設定に関係する落とし穴です。

ホストベース認証でrootによるloginを許可する場合、shosts.equivは参照されません。許可ホスト名は ~root/.shosts で指定する必要があります。

これはrshの仕様である「rootからのloginでは、hosts.equivは参照しない」を踏まえたものですが、ssh関連のマニュアルページには明確な記載がなく、見落としがちなポイントです。

~root/.shostsの指定に当たって注意すべき点は2点です。

第一に、ホスト名を指定に於いては、shosts.equiv同様、sshd_configのUseDNSの値に注意しなければなりません。UseDNSがyesの場合には、正式ホスト名、IPアドレスの何れも指定することが可能です。一方UseDNSがnoの場合には、IPアドレスのみが指定できます。何れの場合においても、別名を利用することは出来ません。

第二に、~root/.shosts はレギュラーファイルでなければならず、所有者がrootでなければならず、且つ、ファイル所有者のみに書き込み許可がなされていなければなりません。symbolic linkであったり、他者が書き込みできるパーミションになっている場合には、このファイルは無視され、結果的に認証が失敗することになります。

またsshd_configの設定にも注意が必要です。

まずrootでログインすることになるため、PermitRootLoginをyesへ設定する必要があります。加えて、~root/.shostsの参照を許可するため、IgnoreRhostsをnoに設定しなければなりません。

結果、ホストベース認証によるrootのloginを許可するためには、sshd_configに対し、最低限以下の記述が必要になります。

HostbasedAuthentication yes
IgnoreRhosts no
PermitRootLogin yes


5. EnableSSHKeysign の設定と ssh-keysign コマンドのパーミション

クライアント側の設定に関係する落とし穴です。

原則ホストの秘密鍵はrootでしか読み取りができないパーミションに設定されています。sshコマンドも例外でなく、一般ユーザによって起動されたsshコマンドは、このファイルを読み取ることが出来ません。その制限を解決するコマンドがssh-keysignです。

ssh-keysignを利用するには、システムワイドなssh_config内でEnableSSHKeysignをyesに設定する必要があります。この設定は、~/.ssh/configで行っても反映されません。加えて、この設定はホストに特化した設定としてではなく、システム全体で有効になるように記載しなければなりません。即ち、全てのHost指定がなされるよりも前で行うか、Hostに*を指定した箇所で行う必要があります。

また、FreeBSDやMacOS Xでは、ssh-keysignコマンドは、デフォルトでrootへsetuidされていません。次のようなコマンドで明示的にrootへsetuidを行う必要があります。この設定を行わない場合、ssh_configでEnableSSHKeysignを設定しても実質機能しません。

# chown root /usr/libexec/ssh-keysign
# chmod u+s /usr/libexec/ssh-keysign


6. ssh_config、~/.ssh/config で指定するホスト名

クライアント側の設定に関係する落とし穴です。

ssh_config や ~/.ssh/config ではホスト名に特化した設定を行うことが可能です。例えば、ホストbar.example.jpへログインする際、必ずホストベース認証を行いたい場合には、以下のように書くことが出来ます。

Host bar.example.jp
 PreferredAuthentications hostbased

ここでHostに続けて指定するホスト名は、sshコマンド実行時の引数に指定するホスト名と完全に合致している必要があります。例えば、bar.example.jpへbarという別名が割り当てられている環境に於いて、

% ssh bar

と指定しても、PreferredAuthentications hostbasedの設定はは有効になりません。この設定を有効にするには、

% ssh bar.example.jp

と指定する必要があります。

ssh_configや~/.ssh/configファイルのホスト名は、空白で区切ることにより複数個指定することが可能です。同一ホストへ別名が付されている場合には、それらを列挙しておくと便利でしょう。

Host bar.example.jp bar
 PreferredAuthentications hostbased


7. 最後に

sshに纏わるトラブルは、サーバ/クライアント双方の設定、認証に使われる鍵、名前解決の仕組み、rsh時代からの慣例などが複雑に絡みあい、問題解決に時間のかかってしまうことが少なくありません。更に、ファイアウォール等によるポート制限などが加わった場合、問題の切り分け自体が困難を伴います。

問題が生じてしまった場合、以下の手法を活用すれば、問題解決の糸口がつかめるかも知れません。

  1. sshd は -d オプションによりデバッグモードで実行させ、且つ -p オプションにより別ポートで待受させる
  2. ssh は -v オプションによりログメッセージを出力させる設定にした上で、-p オプションにより sshd で指定した待ち受けポートへ接続させる
  3. まずは同一ホストでサーバとクライアントの両者を立ち上げ、名前解決に関する問題が原因でないことを突き止める
  4. 安全なネットワークへ実験環境を構築し、ファイアウォールなどのアクセス制限を一旦解除する

ホストベース認証に纏わる問題切り分けの参考になれば幸いです。