PHP の stream_socket_client() でリモートサーバと通信する処理を作っていたときに、引数で指定しているホスト名に対して複数のIPアドレスを返すように DNS サーバで設定していたにも関わらず特定の宛先に常に接続されていたので、その原因を調べてみました。
PHPのソースコードからわかったこと
PHP の socket_connectl() や gethostbyname() や gethostbynamel() の名前解決は gethostbyname() で実装されています。
- https://github.com/php/php-src/blob/php-5.3.17/ext/sockets/sockets.c#L588
- https://github.com/php/php-src/blob/php-5.3.17/ext/standard/dns.c#L245
- https://github.com/php/php-src/blob/php-5.3.17/ext/standard/dns.c#L265
余り詳しくありませんが 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 関係なかった・・・