PHP のソケット通信で1つのホストに複数アドレスが返る場合の優先されるアドレス


PHP の stream_socket_client() でリモートサーバと通信する処理を作っていたときに、引数で指定しているホスト名に対して複数のIPアドレスを返すように DNS サーバで設定していたにも関わらず特定の宛先に常に接続されていたので、その原因を調べてみました。

PHPソースコードからわかったこと

PHP の socket_connectl() や gethostbyname() や gethostbynamel() の名前解決は gethostbyname() で実装されています。


余り詳しくありませんが gethostbyname() は名前解決の問い合わせ結果の順番のままアドレスリストをアプリケーションに返していると思います。
よって bind がラウンドロビンしているのであれば、gethostbyname() で自然とラウンドロビンされるはずです。


一方、PHP の fsockopen() や stream_socket_client() の tcp トランスポートでは getaddrinfo() が名前解決に使われています。

getaddrinfo() と RFC 3484

http://linuxjm.sourceforge.jp/html/LDP_man-pages/man3/getaddrinfo.3.html」 によると、getaddrinfo() で複数のアドレスが返される場合の順番は RFC 3484 で定義されているようです。


RFC3484 インターネット・プロトコルバージョン6(IPv6)のためのデフォルトアドレス選択」の翻訳によると・・・特別な設定をしていなければ↓が影響しそうです。

規則9:最長一致プレフィックスの使用。
もしDAとDBが同じアドレスファミリーに属する場合(共にIPv6であるか、共にIPv4の場合):
もしCommonPrefixLen(DA, Source(DA))>CommonPrefixLen(DB, Source(DB))ならDAが優先です。
同様に、もしCommonPrefixLen(DA, Source(DA))<CommonPrefixLen(DB, Source(DB))なら、DBが優先です。

まとめ

今回試したときのアドレスは次の通りでした。

ソースアドレス
  192.168.0.100

宛先アドレス
  192.168.0.101
  192.168.0.102
  192.168.0.103

この場合 CommonPrefixLen が次のようになるため・・・

CommonPrefixLen(192.168.0.101, 192.168.0.100) → 31
CommonPrefixLen(192.168.0.102, 192.168.0.100) → 30
CommonPrefixLen(192.168.0.103, 192.168.0.100) → 30

192.168.0.101 が常に優先されてラウンドロビンされなかった、ということのようです。

あんまり PHP 関係なかった・・・