Solaris11.3にバージョンアップ後、ネットワーク接続数が多くなると、ソケット接続が失敗する現象が稀に発生し、処理をやり直すことになりました。原因は、TIME_WAITとエフェメラルポートのポート番号の設定方式に起因するとわかたっため、備忘録として記載しました。
#事象
Solaris10からSolaris11.3へバージョンアップ後、サーバ間(サーバA⇒サーバB)のコネクション接続でconnect()から
ECONREFUSED が復帰し、接続不可となる事象が発生しました。
接続数が多い時に発生しましたが、Solaris10の時と、接続数、通信量は変わっていませんでした。
なお、接続ポートにはエフェメラルポートを使用していました。
#原因
ネットワークトレースを取るなどして再現調査を行った結果、以下が判りました。
- コネクションをクローズ後、一定時間(デフォルトは60秒)、そのコネクションをTIME-WAIT状態で保持します。
これはTCPの仕様です。その際、使用していたポート番号もOSで保持しています。 - 次の接続で、ポート番号に直前にクローズされたコネクションで使用していたポート番号(エフェメラルポート)が割り振られました。
そのため、TIME-WAIT状態とみなされ、connectでRSTが復帰されました。
これは、Solaris11からエフェメラルポートがランダムに選択されるように動作が変わったため、まれに直前で使用していたポート番号と同じ番号が割り振られたためでした。
(Solaris10以前は+1カウント方式なので同じポート番号が割り振られる可能性は極めて低いものでした)
SolarisではTIME-WAIT状態の保持期間中に同じクライアントのIPアドレス・ポート番号から
接続要求があった場合はRFC793の規約に準じてRSTを返す実装になっています。
RFC793より新しいRFC1122ではTIME-WAITでも新しい接続要求を受け付けてよいとされており、
WindowsやLinuxはこれに準じているようで、RSTは発生しませんでした。
#対処・教訓
TIME-WAIT時間の変更など考えましたが、他に与える影響を考慮し、
最終的には、RSTが返る場合に備えて、リトライを組み込むようにしました。
実際に3回リトライとし、その後問題は発生していません。
Windows、Linuxでは、この問題は発生しませんが、Solaris転用を考慮する必要がある場合、
connectでRSTが復帰した場合、リトライ処理は組み込んでおくべきかと思います。